# Developer Documentation # Ibexa Developer Documentation # Ibexa Developer Documentation ## How to start? [Check the Requirements](https://doc.ibexa.co/en/latest/getting_started/requirements/index.md) [Install Ibexa DXP](https://doc.ibexa.co/en/latest/getting_started/install_ibexa_dxp/index.md) [Install on Ibexa Cloud](https://doc.ibexa.co/en/latest/ibexa_cloud/install_on_ibexa_cloud/index.md) [Go through the First steps](https://doc.ibexa.co/en/latest/getting_started/first_steps/index.md) ## The latest Ibexa DXP is v5.0 LTS The latest v5.0 LTS release is 5.0.7. You can now update your application. [Release notes](https://doc.ibexa.co/en/latest/release_notes/ibexa_dxp_v5.0/index.md) *[Image: The latest release]* ## The newest LTS Update is Google Gemini connector Integrate Google's AI services into Ibexa DXP. [Learn more about this LTS Update](https://doc.ibexa.co/en/latest/ai_actions/configure_ai_actions/#install-google-gemini-connector) [Discover other LTS Updates](https://doc.ibexa.co/en/latest/ibexa_products/editions/#lts-updates) *[Image: LTS Update]* ## Notable changes in v5.0 - [Collaboration](https://doc.ibexa.co/en/latest/release_notes/ibexa_dxp_v5.0/#collaboration) - [AI Actions](https://doc.ibexa.co/en/latest/release_notes/ibexa_dxp_v5.0/#ai-actions) - [Discounts](https://doc.ibexa.co/en/latest/release_notes/ibexa_dxp_v5.0/#discounts) - [Date and time attribute for product catalog](https://doc.ibexa.co/en/latest/release_notes/ibexa_dxp_v5.0/#date-and-time-attribute) - [Symbol attribute for product catalog](https://doc.ibexa.co/en/latest/release_notes/ibexa_dxp_v5.0/#symbol-attribute) - [Developer experience improvements](https://doc.ibexa.co/en/latest/release_notes/ibexa_dxp_v5.0/#developer-experience) ## Most popular pages - [PHP API](https://doc.ibexa.co/en/latest/api/php_api/php_api/index.md) - [RichText and Online Editor](https://doc.ibexa.co/en/latest/content_management/rich_text/rich_text/index.md) - [Search API](https://doc.ibexa.co/en/latest/search/search_api/index.md) - [Content model](https://doc.ibexa.co/en/latest/content_management/content_model/index.md) - [Images](https://doc.ibexa.co/en/latest/content_management/images/images/index.md) - [Page blocks](https://doc.ibexa.co/en/latest/content_management/pages/page_blocks/index.md) ## Manage your DXP ### [Content](https://doc.ibexa.co/en/latest/content_management/content_management/index.md) - [Content model](https://doc.ibexa.co/en/latest/content_management/content_model/index.md) - [File management](https://doc.ibexa.co/en/latest/content_management/file_management/file_management/index.md) - [Pages](https://doc.ibexa.co/en/latest/content_management/pages/pages/index.md) ### [Product catalog](https://doc.ibexa.co/en/latest/product_catalog/product_catalog/index.md) - [Product catalog configuration](https://doc.ibexa.co/en/latest/product_catalog/product_catalog_configuration/index.md) - [Quable PIM Integration](https://doc.ibexa.co/en/latest/product_catalog/quable_integration/index.md) - [Catalogs](https://doc.ibexa.co/en/latest/product_catalog/catalogs/index.md) - [Prices](https://doc.ibexa.co/en/latest/product_catalog/prices/index.md) ### [Commerce](https://doc.ibexa.co/en/latest/commerce/commerce/index.md) - [Cart](https://doc.ibexa.co/en/latest/commerce/cart/cart/index.md) - [Payment](https://doc.ibexa.co/en/latest/commerce/payment/payment/index.md) - [Storefront](https://doc.ibexa.co/en/latest/commerce/storefront/storefront/index.md) ### [Customer](https://doc.ibexa.co/en/latest/customer_management/customer_portal/index.md) - [Configuration](https://doc.ibexa.co/en/latest/customer_management/cp_configuration/index.md) - [Build Customer Portal](https://doc.ibexa.co/en/latest/customer_management/cp_page_builder/index.md) - [Registration form](https://doc.ibexa.co/en/latest/customer_management/create_user_registration_form/index.md) # Ibexa DXP editions # Ibexa DXP editions Three Ibexa DXP product editions are available to help you accelerate your digital transformation at the speed and cost that work best for you. - [Ibexa Headless](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/ibexa_products/ibexa_headless/): Get to know Ibexa Headless - an edition that focuses on content management. - [Ibexa Experience](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/ibexa_products/ibexa_experience/): Learn about all the main attributes, features, and benefits of the customer-focused Ibexa Experience edition. - [Ibexa Commerce](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/ibexa_products/ibexa_commerce/): Explore all of the key features, functionalities, and advantages of Ibexa Commerce, the most powerful edition that Ibexa DXP has to offer. ## Feature comparison Compare all features available in Ibexa Headless, Ibexa Experience, and Ibexa Commerce to help you choose the right products for your needs: | Feature | Ibexa Headless | Ibexa Experience | Ibexa Commerce | | ------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | ---------------- | -------------- | | [Content model](https://doc.ibexa.co/en/latest/content_management/content_model/index.md) | Yes | Yes | Yes | | [AI Actions](https://doc.ibexa.co/en/latest/ai_actions/ai_actions_guide/index.md) | Yes | Yes | Yes | | [User management](https://doc.ibexa.co/en/latest/users/user_management_guide/index.md) | Yes | Yes | Yes | | [Focus Mode](https://doc.ibexa.co/projects/userguide/en/5.0/getting_started/discover_ui/#focus-mode) | Yes | Yes | Yes | | [Image editor](https://doc.ibexa.co/projects/userguide/en/5.0/image_management/edit_images/) | Yes | Yes | Yes | | [Content scheduler](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/schedule_publishing/) | Yes | Yes | Yes | | [SEO](https://doc.ibexa.co/projects/userguide/en/5.0/search_engine_optimization/seo/) | Yes | Yes | Yes | | [Content translation](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/translate_content/) | Yes | Yes | Yes | | [Search](https://doc.ibexa.co/projects/userguide/en/5.0/search/search_for_content/) | Yes | Yes | Yes | | [Editorial workflow](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/workflow_management/editorial_workflow/) | Yes | Yes | Yes | | [Digital Asset Management](https://doc.ibexa.co/projects/userguide/en/5.0/dam/ibexa_dam/) | Yes | Yes | Yes | | [Product catalog capabilities](https://doc.ibexa.co/projects/userguide/en/5.0/pim/pim/) | Yes | Yes | Yes | | [Date and time attribute type](https://doc.ibexa.co/en/latest/product_catalog/attributes/date_and_time/index.md) | Yes | Yes | Yes | | [Symbol attribute type](https://doc.ibexa.co/en/latest/product_catalog/attributes/symbol_attribute_type/index.md) | Yes | Yes | Yes | | [Personalization](https://doc.ibexa.co/en/latest/personalization/personalization_guide/index.md) | Yes | Yes | Yes | | [Migrations](https://doc.ibexa.co/en/latest/content_management/data_migration/managing_migrations/index.md) | Yes | Yes | Yes | | [Ibexa Connect](https://doc.ibexa.co/projects/connect/en/latest) | Yes | Yes | Yes | | [OAuth client](https://doc.ibexa.co/en/latest/users/oauth_client/index.md) | Yes | Yes | Yes | | [OAuth Server](https://doc.ibexa.co/en/latest/users/oauth_server/index.md) | Yes | Yes | Yes | | [Site Factory](https://doc.ibexa.co/en/latest/multisite/site_factory/site_factory/index.md) | | Yes | Yes | | [Customizable Dashboard](https://doc.ibexa.co/projects/userguide/en/5.0/getting_started/dashboard/work_with_dashboard/#customize-dashboard) | | Yes | Yes | | [Page Builder](https://doc.ibexa.co/en/latest/content_management/pages/page_builder_guide/index.md) | | Yes | Yes | | [Form Builder](https://doc.ibexa.co/en/latest/content_management/forms/form_builder_guide/index.md) | | Yes | Yes | | [Scheduler tab](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/schedule_publishing/#scheduler-tab) | | Yes | Yes | | [Content Scheduler block](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/schedule_publishing/#content-scheduler-block) | | Yes | Yes | | [Corporate account management](https://doc.ibexa.co/projects/userguide/en/5.0/customer_management/manage_customers/) | | Yes | Yes | | [Customer Portal](https://doc.ibexa.co/en/latest/customer_management/customer_portal_guide/index.md) | | Yes | Yes | | [Segments](https://doc.ibexa.co/en/latest/administration/admin_panel/segments_admin_panel/index.md) | | Yes | Yes | | [Recent activity](https://doc.ibexa.co/en/latest/administration/recent_activity/recent_activity/index.md) | | Yes | Yes | | [Ibexa Engage add-on](https://doc.ibexa.co/projects/userguide/en/5.0/ibexa_engage/ibexa_engage/) | | Yes | Yes | | [Customer Data Platform (CDP) add-on](https://doc.ibexa.co/en/latest/cdp/cdp_guide/index.md) | | Yes | Yes | | [Order management](https://doc.ibexa.co/en/latest/commerce/order_management/order_management/index.md) | | | Yes | | [Payment management](https://doc.ibexa.co/en/latest/commerce/payment/payment/index.md) | | | Yes | | [Shipping management](https://doc.ibexa.co/en/latest/commerce/shipping_management/shipping_management/index.md) | | | Yes | | [Cart](https://doc.ibexa.co/en/latest/commerce/cart/cart/index.md) | | | Yes | | [Checkout](https://doc.ibexa.co/en/latest/commerce/checkout/checkout/index.md) | | | Yes | | [Storefront](https://doc.ibexa.co/en/latest/commerce/storefront/storefront/index.md) | | | Yes | | [Transactional emails](https://doc.ibexa.co/en/latest/commerce/transactional_emails/transactional_emails/index.md) | | | Yes | | [Discounts](https://doc.ibexa.co/en/latest/discounts/discounts/index.md) | | | Yes | ## LTS Updates LTS Updates are opt-in packages that bring additional features to the [LTS releases](https://doc.ibexa.co/en/latest/resources/release_process_and_roadmap/#long-term-support-releases) that they enhance. The features brought by LTS Updates become standard parts of the next LTS release. | Feature | Ibexa Headless | Ibexa Experience | Ibexa Commerce | | -------------------------------------------------------------------------------------------------------------------------- | -------------- | ---------------- | -------------- | | [Anthropic connector](https://doc.ibexa.co/en/latest/ai_actions/configure_ai_actions/#install-anthropic-connector) | Yes | Yes | Yes | | [Google Gemini connector](https://doc.ibexa.co/en/latest/ai_actions/configure_ai_actions/#install-google-gemini-connector) | Yes | Yes | Yes | | [Integrated help](https://doc.ibexa.co/en/latest/administration/back_office/integrated_help/index.md) | Yes | Yes | Yes | | [Shopping list](https://doc.ibexa.co/en/latest/commerce/shopping_list/shopping_list_guide/index.md) | | | Yes | # Ibexa Headless edition product guide ## What is Ibexa Headless The Headless edition of Ibexa DXP focuses on content management. It provides tools to collaboratively create content, and interfaces (API) to distribute this content. Multilingual, multichannel, extensible, Ibexa Headless is an advanced Content Management Framework (CMF) with product catalog capabilities, and a Digital Asset Management (DAM) repository. It's provided without a default front office, but with a complete back office and several APIs to manage and access content. *[Image: Ibexa Headless]* ## Availability To start using Ibexa Headless you must purchase a product license. For more information, see [Ibexa Headless license pricing](https://www.ibexa.co/products/pricing?tab=1). You can [contact us](https://www.ibexa.co/about-ibexa/contact-us) or [contact one of our partners](https://www.ibexa.co/partners). ## How it works ### Editorial stage You access with any web browser from any platform to a rich back office, the main place to - define users and their rights (for example, customers, subscribers, or editors), - organize content (content types, fields, tree, tags, languages, and more), - edit content in a collaborative workplace with versions and workflows. Then, content is available to end users through REST, GraphQL, or every output you can imagine like websites or apps. ### Technical backstage When you have a license, you install Ibexa Headless through Composer on an architecture including at least a web server with PHP and a relational database server. For performance, several bricks can be added to your stack such as a reverse proxy or a search engine. Ibexa Headless is based on Symfony. Any Symfony developer, or even PHP developer, can quickly learn how to extend it with the help of an online documentation. By using a version control system and environment variables, you can deploy your configuration and extensions on several environments including Ibexa Cloud. Standard web APIs and [Ibexa Connect](https://doc.ibexa.co/projects/connect/en/latest/general/ibexa_connect/) help establish interoperability, even if you aren't an advanced developer. *[Image: Ibexa Headless data inputs and outputs]* APIs summary: - The REST and GraphQL APIs give access to the content in standardized ways. - The OAuth 2 [Client](https://doc.ibexa.co/en/latest/users/oauth_client/index.md) and [Server](https://doc.ibexa.co/en/latest/users/oauth_server/index.md) allow to connect to an SSO or be the SSO. - The design engine and its theme templates mechanism allows to serve the content in several shapes. - The PHP API opens Ibexa Headless to extendability to fit your needs. For example, content can be computed, edited, or served in specific ways such as scheduled/live imports/exports, automated edition tasks, or specific controllers to communicate with other applications. ## Capabilities and benefits Ibexa Headless is a tool box with a back office. It comes without a default front office. You don't lose time to develop a theme for a provided front office before discovering it doesn't fit your needs. No distraction. Ibexa Headless helps you focus on the content, create and organize with its straightforward user interface (UI), imagine its inputs/outputs, and implement them with its various layers' APIs. ### Core features The core of Ibexa Headless offers everything to structure your content repositories and access them. #### Content model Content modeling and management are the foundation of Ibexa DXP with the following main layers: - Content items are organized as a tree in a repository. - An item can have multiple locations in this tree. - Content items are typed. - Content types are sets of typed data fields, with optional conditions on the possible values. - Rich Text field type comes with an [online editor](https://doc.ibexa.co/en/latest/content_management/rich_text/online_editor_guide/index.md). - Multilingual, it can store a content in several languages, the content model defines which field must be translated, and which don't vary. For more information, see [Content management product guide](https://doc.ibexa.co/en/latest/content_management/content_management_guide/index.md). #### User management User and user group rights are set by roles with thin granular limited permission policies in a safe deny-by-default security system. Users are content items as well, so your knowledge about content management is reused. For more information, see [User management product guide](https://doc.ibexa.co/en/latest/users/user_management_guide/index.md). #### Content access There are many paths to access the content in many shapes: - The REST API and GraphQL API support access to, and edition of the content. - Ibexa Headless offers a complete PHP API to extend the ways to access content. - A design engine and a view controller offer to create plain text content views (such as HTML, JSON, XML, CSS, JS, CSV, or Markdown), and to factorize those views by using theme cascades. This design engine is used in the back office which is equally extendable. - Multichannel, content can be accessed through several channel configurations, such as the domain name it replies to, the sub-part of the content tree it starts from, the users rights, or the design theme. The back office itself is such a channel. - Multi-repository, the same platform can use separate databases if data isolation is needed between channel groups. ### Advanced features On top of this strong core, Ibexa Headless brings tools to increase user experience, from final front users to back office contributors. #### Complete DXP Ibexa Headless is a complete Digital Experience Platform (DXP), which comes with the following components to enhance user's journey: - [Personalization](https://doc.ibexa.co/en/latest/personalization/personalization_guide/index.md) engine, which allows you to recommend content to end users according to their behavior, or, when authenticated, by matching with their segment/group. - Content scheduler, which allows you to establish the future of the content and use events to have a living front application, even when the editorial team is absent or reduced. This way, visitors can discover new content at midnight, during weekends or vacations. A calendar summarises those scheduled content events. Like everything in the back office, the calendar is extendable: you can add an event source to coordinate content events with other company events. #### Many ways to structure and organize content [Product catalog](https://doc.ibexa.co/en/latest/product_catalog/product_catalog_guide/index.md) helps organize complex products and their catalogs: - Products are organized by using product types, variants, catalogs, categories, and tags. - Product attributes are grouped and factorized among product types. For example, fabric + color + size can be shared by many clothing product types. - Product variants can rapidly be created by the automatic declination of attributes that have a defined set of values. - With taxonomy, you can tag content items to organize them by topics in a much intuitive way for the editor than a content tree with multiple locations would. Tags themselves are organized in a tree, and synonyms are linked to favorite terms. Tag organization can be handled by a supervisor who doesn't need to move content items around a corporate content tree. At search time, tags can be keywords with a high value in relevance score to help the end user having results closer to the searched topic. #### Collaboration Several features help end users collaborate on the content, such as: - Version comparison helps track changes and solve concurrent editing conflicts. - Workflows helps with collaborative editing chain. A built-in “Quick review“ workflow allows an editor to send a content draft to a colleague for review, and comment or publishing. But, as a framework, more complex workflows can be imagined, with several steps and paths, even some automated tasks. #### Accelerated content editing - Ibexa Headless's content tree has several actions available directly on its items. For example, no need to open a content to hide it, you can do it directly from the content tree. - An Image Editor offers to crop and flip images. When serving the image in various context, you can even set a focal point to indicate to automated cropping which part of the image should be kept. - A Digital Asset Management (DAM) helps you crawl through your image resources to use and reuse them in your content. And a DAM connector allows you to search for images hosted on third party DAM servers. - [AI Actions](https://doc.ibexa.co/en/latest/ai_actions/ai_actions_guide/index.md) help you automate time-consuming editorial tasks. #### Network integration ##### Intranets and extranets - Ibexa Connect's role is to create application interconnections with low code and drag-and-drop, in a compelling visual interface. Complex data flows can be easily implemented with a huge library of connectors and actions for famous to specific applications. For more information, see [Ibexa Connect product guide](https://doc.ibexa.co/projects/connect/en/latest/general/ibexa_connect/). - An OAuth 2 server offers the possibility to use the DXP as the authentication service for other applications. - An OAuth 2 client supports authentication with a third-party OAuth 2 server. - A DAM Connector, previously mentioned, helps to access any image repository when needing to illustrate a content. - Ibexa Headless supports Elasticsearch and Solr. It gives the choice between using Solr or Elasticsearch as a search engine, whether hosted on Ibexa Cloud or on-premises. This choice might be influenced by technology you already use, or you want to invest in for other internal projects. - Ibexa Headless offers to export and import from command line part of the content model or content items. For example, it can be used to move new content types and items from a staging instance to the production one. ##### Internet, delivery, web search engines, and social networks - Ibexa Headless comes with the support of Fastly content delivery network (CDN). The HTTP cache varies on current user's role and is purged when content changes. With its huge network of points of presence (POP) around the world, Fastly is quickly delivering cached content from nearest server for a better user experience. - A Search Engine Optimization (SEO) field implements best practices about web search engine indexing and social network sharing. It covers canonical URLs which are mandatory if multiple locations are used for a same content item to avoid duplicate content, Open Graph protocol to better describe a content item to social networks and search engine, and Twitter Cards. ## Use cases As a content repository with an omnipotent back office, many APIs to absorb, compute and distribute content, even a recommendation engine to deliver the right content to various readers, Ibexa Headless can be used in several cases. Here are few examples. ### Brick and mortar, but with an online showcase If you prefer the human warmth of a retail store, if your products' numerous complex options should be discussed, or if you're not ready yet to sell online, Ibexa Headless helps to build an exposition of your product catalog and your philosophy, an online presence to keep earlier customers interested and gather new ones. It can be a structuring first step to test customer's adoption of your website, before increasing user experience with Ibexa Experience, and finally becoming an online store with Ibexa Commerce. ### Large network with multiple inputs and outputs Departments, subsidiaries, and even partners now produce content in the same repository from the same collaborative workspace. Thanks to migration feature and PHP API, existing content has been imported from previous repositories. Fine-tuned user rights and workflows ensure that each collaborator can focus on their own tasks without the risk to disturb the content model or content organization. Content is distributed on several websites and applications, some running on the Ibexa platform itself, some on third parties' servers, some as native mobile apps. Part of the content has multiple locations or is translated, and reused from place to place. While the back office offers to search into the whole repository, the front end apps have correctly circumscribed search capabilities. # Ibexa Experience edition product guide ## What is Ibexa Experience Ibexa Experience is an Ibexa DXP edition that focuses on the customer. It offers smooth consumer journey and great online experience. In everything you do, it places your clients first. With Experience edition you can empower Editors to quickly create new pages or personalized content, and improve their daily work. It also provides tools for using segmentation and targeting, and it can be widely used in B2B thanks its features and integrations. *[Image: Ibexa Experience]* ## Availability To start using Ibexa Experience, you need to purchase a product license. For more information, see [Ibexa Experience license pricing](https://www.ibexa.co/products/pricing?tab=2). You can also [contact us](https://www.ibexa.co/about-ibexa/contact-us) or [one of our partners](https://www.ibexa.co/partners). ## How it works ### Technical backstage With an active license, you can start the [installation process](https://doc.ibexa.co/en/latest/getting_started/install_ibexa_dxp/index.md) that uses the Composer. Ibexa Experience is based on [Symfony](https://symfony.com/doc/7.4). With a help of documentation and trainings, any developer familiar with Symfony or simply PHP may learn how to use available extension points and extend the platform. Ibexa Experience is built on top of [Ibexa Headless](https://doc.ibexa.co/en/latest/ibexa_products/ibexa_headless/index.md), therefore it includes all bundles, APIs, and [features that come with Headless edition](https://doc.ibexa.co/en/latest/ibexa_products/ibexa_headless/#core-features), but also more advanced features for digital experience management. Version control systems and environment variables allow you to deploy your projects and settings on several environments, such as Ibexa Cloud. ## Capabilities and benefits With Ibexa Experience you can focus on your customers and treat each one as a VIP. It has everything that you may need to offer a transformative digital experience, from developing new websites or portals, through eye-catching landing pages and personalized product suggestions, to managing SEO strategies across several locations. ### Core features Ibexa Experience comes with a variety of new features designed to help you create an exceptional customer experience. #### Page Builder Ibexa Experience brings the [Page Builder](https://doc.ibexa.co/en/latest/content_management/pages/page_builder_guide/index.md), a powerful visual tool that helps you design and modify pages, without requiring advanced technical skills. With its intuitive and user-friendly interface, you can develop pages, tailor content, and create perfectly targeted landing pages. You build pages from ready-to-use elements called blocks, which can be easily configured and customized to suit your needs. Before you start building a page, you also need to select a layout. It has a significant impact on how the content pieces in the drop zones are arranged. *[Image: Page Builder]* #### Form Builder [Form Builder](https://doc.ibexa.co/en/latest/content_management/forms/form_builder_guide/index.md) is an intuitive tool that allows you to transform user engagement on your website. With this tool, you can design, deploy, and manage online forms quickly. You can create a variety of forms that consist of different fields, including sign-up forms, surveys, or questionnaires. Additionally, you can monitor and manage the information obtained from website visitors and adjust your forms if needed. *[Image: Form Builder]* #### Site Factory [Site Factory](https://doc.ibexa.co/en/latest/multisite/site_factory/site_factory/index.md) is a site management interface, integrated with the back office. It enables you to configure new sites without leaving the administration interface and editing SiteAccess configuration. With this feature you can create and deploy multiple websites at lightning speed and at scale. It allows you to manage expenses and resources while industrializing your web presence. Additionally, together with localized information and tailored product catalogs and prices, it helps you to quickly enter new markets. #### Customizable dashboard Starting from Experience edition of Ibexa DXP you can [customize the dashboard](https://doc.ibexa.co/projects/userguide/en/5.0/getting_started/dashboard/work_with_dashboard/#customize-dashboard), and you do it with the Dashboard Builder. You can tailor dashboard to your specific needs by choosing from a set of widgets. You can easily preview the sections that you use more often and omit the less significant ones. *[Image: Customizable dashboard]* #### Publish Later You can take complete control of where and when your content blocks are visible to your predefined audiences and [schedule content publication](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/schedule_publishing/). Ibexa Experience comes with a Publish Later feature that allows you to schedule personalized content and reach different user groups at optimal dates and times to boost performance. What's more, you can turn specific content pages and blocks on and off to meet the needs of your marketing campaigns and promotions. Publish Later feature combined with Page Builder allows you to see all changes that you plan for the future. To do it, just use the slider to see all the upcoming changes. #### Customer Portal Use the [Customer Portal](https://doc.ibexa.co/en/latest/customer_management/customer_portal/index.md) and customer management capabilities that come with it, to establish new corporate accounts, manage existing ones, and communicate with your partners within a personalized space. With the help of this feature, you can create customized areas that give users a smooth, integrated experience and provide them with access to a variety of resources, apps, and services from a single point of entry. Using this tool, your customers can change their organization details, invite and see members, self-register, and more. #### Segments [Segmentation](https://doc.ibexa.co/en/latest/administration/admin_panel/segments_admin_panel/) allows you to split up the user base. By assigning users to segments, you can display specific content to selected visitors and tailor the content that they can see. One of the tools that you can use right out of the box is the Targeting block that is available in the Page Builder. Segmentation is also useful with the [Personalization](https://doc.ibexa.co/en/latest/personalization/personalization_guide/index.md). You can assign users to different recommendation groups and create advanced logic with operators to provide your audience with the best recommendations. *[Image: Segments]* #### Customer Data Platform (CDP) [Ibexa CDP](https://doc.ibexa.co/en/latest/cdp/cdp_guide/index.md) is an add-on available for both Experience and [Commerce](https://doc.ibexa.co/en/latest/ibexa_products/ibexa_commerce/index.md) editions of Ibexa DXP. To use it, you must make arrangements with Ibexa to define the initial configuration. Once you activate Ibexa CDP, you can create complete customer profiles, including their interactions, behavior, and preferences. It helps you improve user engagement, conversion rates, and return on investment by segmenting your audience and delivering tailored campaigns and experiences. Additionally, you can manage and analyze campaigns, evaluate customer data, and identify the best ways to improve performance. By using Ibexa CDP you can store and manage large volumes of customer data in a structured manner. This central data storage supports business growth with a scalable infrastructure, helping to futureproof your business. *[Image: CDP]* #### Ibexa Engage Another add-on available for Experience and [Commerce](https://doc.ibexa.co/en/latest/ibexa_products/ibexa_commerce/index.md) edition is [Ibexa Engage](https://doc.ibexa.co/en/latest/ibexa_engage/ibexa_engage/index.md). To use it, you must make arrangements with Ibexa to define the initial configuration, and then get and set up a user account. Ibexa Engage is a data collection tool. It gives you the ability to use the [Qualifio](https://qualifio.com/) tools to engage your audiences. You can use Qualifio's existing templates and interactive elements, such as quizzes, pools, and forms, to create visually appealing, customized campaigns and collect important data. To promote your campaign, you can add a Campaign block to a page in Page Builder or embed a campaign within the Rich Text field by using a Campaign custom tag. *[Image: Ibexa Engage]* ### Use cases With Ibexa Experience, your customers are the main focus of all that you do. It makes it simpler than ever to create the different touchpoints that your customers have with your brand, giving you the ability to guide them through your significant business procedures. #### Build new pages and integrated forms User interface of Ibexa Experience is intuitive and plain. With its new features - Page and Form Builder - you can build new pages or forms quickly efficiently. Page Builder comes with predefined layouts, blocks, and templates to streamline your design process, while Form Builder provides ready-to-use elements for easy form creation. You can integrate your custom forms and surveys into the website and reuse content from existing sites on new ones. With Site Factory, you can publish as many sites as you like, there are no limits. #### Target customers in their preferred channels To make your products attractive, you must remember each of your customers is unique and special, and tailor your marketing strategy to their needs and preferences. Ibexa Experience allows you to deliver personalized content and recommendations through different channels. With segmentation, you can define audiences to distribute specific content through the right channels, at the right time. Available add-ons give you even more possibilities. You can analyze customer behaviours, use interactive content, and collect important data. #### Build your future Planning and scheduling is a part of management. With scheduling tools available in Experience edition, you can prepare content publishing timetables, schedule how your website can evolve, and test or preview it before publication. Additionally, you can use editorial calendars for an easier collaboration. # Ibexa Commerce edition product guide ## What is Ibexa Commerce Ibexa Commerce is the most powerful edition offered by Ibexa. It assists you in managing each aspect of your customers' journey by combining content management, customization, and commerce functions into a single, dedicated solution. Ibexa Commerce offers a streamlined, unified platform where you can personalize each aspect of the online shopping experience. You can completely revamp your online stores and give your consumers exceptional purchasing experiences, from first contact to post-purchase support. *[Image: Ibexa Commerce]* ## Availability To start using Ibexa Commerce, you need to purchase a product license. For more information, see [Ibexa Commerce license pricing](https://www.ibexa.co/products/pricing?tab=3). You can also [contact us](https://www.ibexa.co/about-ibexa/contact-us) or [one of our partners](https://www.ibexa.co/partners). ## How it works ### Technical backstage With an active license, you can start the [installation process](https://doc.ibexa.co/en/latest/getting_started/install_ibexa_dxp/index.md) that uses the Composer. Ibexa Commerce is based on [Symfony](https://symfony.com/doc/7.4). With a help of documentation and trainings, any developer familiar with Symfony or even PHP alone can learn how to use available extension points and extend the platform. Version control systems and environment variables allow you to deploy your extensions and settings on several environments, such as [Ibexa Cloud](https://doc.ibexa.co/en/latest/ibexa_cloud/ibexa_cloud_guide/index.md). Ibexa Commerce is built on [Ibexa Experience](https://doc.ibexa.co/en/latest/ibexa_products/ibexa_experience/index.md) and includes all bundles, APIs, and features that come with both [Ibexa Headless](https://doc.ibexa.co/en/latest/ibexa_products/ibexa_headless/#core-features) and [Ibexa Experience](https://doc.ibexa.co/en/latest/ibexa_products/ibexa_experience/#core-features) editions. ## Capabilities and benefits With Ibexa Commerce you can focus on accelerating your transformation into a fully-fledged eCommerce. It comes with all the necessary tools: customized catalogs, integration with CDP, personalized checkout workflows, payment gateway integration, transactional emails, and more. ### Core features Ibexa Commerce includes all the features you need to launch your online store and reduce the time it takes to go live. #### Order management With the advanced [Order management](https://doc.ibexa.co/projects/userguide/en/5.0/commerce/order_management/order_management/) tools, you can manage orders with ease. Depending on your permissions, you can search for orders, review their details and updates, track completion status, and cancel orders that are created when store customers purchase products. When searching for orders, you can use filters to save time and reduce effort. Order management is strongly connected with other components of the Commerce offering, such as [Cart](https://doc.ibexa.co/en/latest/commerce/cart/cart/index.md) and [Checkout](https://doc.ibexa.co/en/latest/commerce/checkout/checkout/index.md), so users can speed up the process by uploading an order list or repeating previous transactions. *[Image: Order management]* #### Payment management The [Payment](https://doc.ibexa.co/en/latest/commerce/payment/payment/index.md) component allows users to search for payment methods and payments, create new and manage existing payments and payment methods, and filter search results. Users can also enable or disable payment methods, change payment details, and cancel payments, depending on their role. *[Image: Payment management]* #### Shipping management With the [Shipping](https://doc.ibexa.co/en/latest/commerce/shipping_management/shipping_management/index.md) component users can create and manage shipments, search for shipments, filter search results, and define and manage various shipping methods. Depending on their role, users can also enable or disable shipping methods, change status of shipments, and cancel shipments. *[Image: Shipping management]* #### Storefront The [Storefront](https://doc.ibexa.co/en/latest/commerce/storefront/storefront/index.md) package includes a starter kit for developers. It's a foundational set of components that developers can customize and extend to create their own web store implementations. It contains default UI components and widgets that can be modified to [create a customized web store](https://doc.ibexa.co/en/latest/templating/layout/customize_storefront_layout/index.md). #### Relevant faceted search Search becomes crucial when your product catalog is extensive. Products can be sorted according to a variety of criteria using faceted search. The value it brings makes it a vital component in merchandising. You can set up your search engine using Ibexa Commerce to help clients find what they're looking for more easily, which could result in more purchases. #### Catalog management Ibexa Commerce gives you the ability to manage your product repository - [Product catalog](https://doc.ibexa.co/en/latest/product_catalog/product_catalog_guide/index.md), and construct an infinite number of catalogs, each with unique prices, to further customize the experience for your customers. #### Transactional emails Commerce allows you to send transactional emails - messages that Ibexa can send through the [Actito](https://www.actito.com/en-BE/) gateway to your end-users. These emails include notifications about changes in the status of various actions taken in relation to your commerce presence. With this feature you can also [create email campaigns](https://doc.ibexa.co/en/latest/commerce/transactional_emails/transactional_emails/#create-email-campaigns) to engage users and increase sales. ### Use cases #### Create personalized shipping experience Use [Ibexa Personalization](https://doc.ibexa.co/projects/userguide/en/5.0/personalization/use_cases/#ecommerce) to transform your online stores and give your consumers great buying experiences, from initial contact to post-purchase support. No matter how complicated your product or sales process are, you can present your offer in an approachable way. Creating engaging and personalized shopping experiences with targeted offers and recommendations helps you boost sales. Within eCommerce, product recommendations can assist users in finding the exact item that meets their needs. Recommendations can be used to propose related, alternative, or complimentary products to users who are unsure what to buy. #### Use effective merchandising Merchandising assists in keeping the consistency of the brand and providing customized product recommendations with captivating visuals and powerful search features. You can engage your customers with eye-catching graphics and information. The search engine makes it easy to find what they're looking for by providing quick and easy access to the product catalogs. The customer experience takes an important step forward by facilitating financial transactions through the use of powerful, individualized product suggestions provided by Ibexa Commerce and unique pricing for various customer groups. #### Launch consumer-facing web stores Ibexa Commerce comes with all the features you need to launch and manage your web store, like storefront starter kit, real-time cart management, stock inventory, catalog management, and more. This edition is designed for complex enterprises and is fully customizable. It enables you to design flawless sales experiences regardless of the complexity of your business model. You can create online store that really fits your needs. #### Increase B2B sales Ibexa Commerce contains the best B2B features to help you speed up your digital transformation, such as corporate account management, tailored catalogs, customized workflows, effortless reordering, custom pricing, and more. #### Automate business processes With Ibexa Commerce, the automation process becomes easier, which is essential if your company wants to do more with less effort. It comes with all capabilities, including order and inventory management, customer data, and custom pricing, which are needed to achieve it. You can integrate with over 1,300 standard apps, including your CRM, ERP, PIM, and DAM systems, and build custom connectors. Also, you can use the ready-to-use pre-designed templates. # Getting started # Getting started To get started working with Ibexa DXP, see how you can get an installation and what first steps to take to familiarize yourself with the platform. - [Requirements](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/getting_started/requirements/): System, component and package requirements for running Ibexa DXP. - [Install Ibexa DXP](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/getting_started/install_ibexa_dxp/): Install Ibexa DXP on a Linux system and prepare your installation for production. - [First steps](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/getting_started/first_steps/): Start off working with Ibexa DXP by doing initial configuration and testing system capabilities. # Requirements This document covers all supported versions of the product. To review the requirements, select the specific version of Ibexa DXP you're interested in. The following server requirements cover both running the software on-premise and on third-party PaaS providers. > **Note: Ibexa Cloud** > > For running on [Ibexa Cloud](https://www.ibexa.co/products/ibexa-cloud), where recommended configuration and support is provided out of the box, see separate [Ibexa Cloud section](#ibexa-cloud-requirements-and-setup) for further reading on its requirements. The minimal setup requires PHP, MySQL/MariaDB, Apache/Nginx, Node.js and `yarn`. For production setups it's recommended that you use Varnish/Fastly, Redis/Valkey, NFS/EFS/S3 and Solr/Elasticsearch in a [clustered setup](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/clustering/clustering/index.md). > **Caution: Recommended versions** > > Review all the recommended versions carefully. If you see a "+" next to the product version, it means that we recommend this version or higher within the same major release. For example, "1.18+" means any 1.x version higher or equal to 1.18, but not 2.x. > > Using the latest listed version of each product or component is recommended. Always use a version that receives security updates, either by the vendor themselves or by a trusted third party, such as the distribution vendor. ## Operating system **Ibexa DXP v5.0** | Name | Version | | ----------------------------- | ---------- | | Debian 11 "Bullseye" | 11.0-11.7+ | | Ubuntu "Noble Numbat" | 24.04 | | RHEL / CentOS / CentOS Stream | 8.1-9.5+ | If you see a "+" next to the product version, it indicates a recommended version or higher within the same major release. For example, "1.18+" means any 1.x version equal to or higher than 1.18, but not 2.x. **Ibexa DXP v4.6** | Name | Version | | ----------------------------- | ----------- | | Debian 10 "Buster" | 10.0-10.13+ | | Debian 11 "Bullseye" | 11.0-11.7+ | | Ubuntu "Focal Fossa" | 20.04 | | Ubuntu "Jammy Jellyfish" | 22.04 | | Ubuntu "Noble Numbat" | 24.04 | | RHEL / CentOS / CentOS Stream | 8.1-9.5+ | If you see a "+" next to the product version, it indicates a recommended version or higher within the same major release. For example, "1.18+" means any 1.x version equal to or higher than 1.18, but not 2.x. **Ibexa DXP v3.3** | Name | Version | | ------------------------ | ----------- | | Debian 10 "Buster" | 10.0-10.13+ | | Debian 11 "Bullseye" | 11.0-11.7+ | | Ubuntu "Focal Fossa" | 20.04 | | Ubuntu "Jammy Jellyfish" | 22.04 | | RHEL / CentOS | 8.1-8.5+ | If you see a "+" next to the product version, it indicates a recommended version or higher within the same major release. For example, "1.18+" means any 1.x version equal to or higher than 1.18, but not 2.x. ## Web server **Ibexa DXP v5.0** - Nginx 1.27+ - Apache 2.4 (with required modules `mod_rewrite`, `mod_env` and recommended: `mod_setenvif`, `mod_expires`; event MPM is recommended, if you need to use prefork you also need the `mod_php` module) If you see a "+" next to the product version, it indicates a recommended version or higher within the same major release. For example, "1.18+" means any 1.x version equal to or higher than 1.18, but not 2.x. **Ibexa DXP v4.6** - Nginx 1.18-1.25+ - Apache 2.4 (with required modules `mod_rewrite`, `mod_env` and recommended: `mod_setenvif`, `mod_expires`; event MPM is recommended, if you need to use prefork you also need the `mod_php` module) If you see a "+" next to the product version, it indicates a recommended version or higher within the same major release. For example, "1.18+" means any 1.x version equal to or higher than 1.18, but not 2.x. **Ibexa DXP v3.3** - Nginx 1.18 - Apache 2.4 (with required modules `mod_rewrite`, `mod_env` and recommended: `mod_setenvif`, `mod_expires`; event MPM is recommended, if you need to use prefork you also need the `mod_php` module) ## DBMS **Ibexa DXP v5.0** - MariaDB 10.11+ or 11.4 - MySQL 8.4 - PostgreSQL 14 or 18 If you see a "+" next to the product version, it indicates a recommended version or higher within the same major release. For example, "1.18+" means any 1.x version equal to or higher than 1.18, but not 2.x. **Ibexa DXP v4.6** - MariaDB 10.3-10.11+ or 11.4 - MySQL 8.0 or 8.4 - PostgreSQL 14 or 18 If you see a "+" next to the product version, it indicates a recommended version or higher within the same major release. For example, "1.18+" means any 1.x version equal to or higher than 1.18, but not 2.x. **Ibexa DXP v3.3** - MariaDB 10.3, 10.4 (optionally 10.2 - deprecated) - MySQL 8.0 (optionally 5.7 - deprecated) - PostgreSQL 10+ (PostgreSQL 10 has reached its End of Life. We highly recommend using PostgreSQL 14 for optimal performance and security) If you see a "+" next to the product version, it indicates a recommended version or higher within the same major release. For example, "1.18+" means any 1.x version equal to or higher than 1.18, but not 2.x. ## PHP **Ibexa DXP v5.0** - 8.4 - 8.3 **Ibexa DXP v4.6** - 8.4 - 8.3 - 8.2 - 8.1 (PHP 8.1 has reached its End of Life. Unless you have extended support from vendors like Debian or Zend, you should use PHP 8.2) - 8.0 (PHP 8.0 has reached its End of Life. Unless you have extended support from vendors like Debian or Zend, you should use PHP 8.2) - 7.4 (PHP 7.4 has reached its End of Life. Unless you have extended support from vendors like Debian or Zend, you should use PHP 8.2) **Ibexa DXP v3.3** - 8.3 - 8.2 - 8.1 - 8.0 (PHP 8.0 has reached its End of Life. Unless you have extended support from vendors like Debian or Zend, you should use PHP 8.1) - 7.4 (PHP 7.4 has reached its End of Life. Unless you have extended support from vendors like Debian or Zend, you should use PHP 8.1) - 7.3 (PHP 7.3 has reached its End of Life. Unless you have extended support from vendors like Debian or Zend, you should use PHP 8.1) ### PHP extensions **Ibexa DXP v5.0** - `php-cli` - `php-fpm` - `php-mysql` (`php-mysqlnd`) or `php-pgsql` - `php-xml` - `php-mbstring` - `php-process` (on RHEL/CentOS) - `php-intl` - `php-curl` - `php-pear` (optional, provides pecl) - `php-gd` or `php-imagick` (via pecl on RHEL/CentOS) - `php-sodium` - `php-bcmath` **Ibexa DXP v4.6** - `php-cli` - `php-fpm` - `php-mysql` (`php-mysqlnd`) or `php-pgsql` - `php-xml` - `php-mbstring` - `php-json` - `php-process` (on RHEL/CentOS) - `php-intl` - `php-curl` - `php-pear` (optional, provides pecl) - `php-gd` or `php-imagick` (via pecl on RHEL/CentOS) - `php-sodium` - `php-bcmath` **Ibexa DXP v3.3** - `php-cli` - `php-fpm` - `php-mysql` (`php-mysqlnd`) or `php-pgsql` - `php-xml` - `php-mbstring` - `php-json` - `php-process` (on RHEL/CentOS) - `php-intl` - `php-curl` - `php-pear` (optional, provides pecl) - `php-gd` or `php-imagick` (via pecl on RHEL/CentOS) - `php-sodium` ### Cluster PHP extensions **Ibexa DXP v5.0** - `php-redis` **Ibexa DXP v4.6** - `php-redis` or `php-memcached` **Ibexa DXP v3.3** - `php-redis` or `php-memcached` ## Search **Ibexa DXP v5.0** | Name | Version | | ------------- | ----------------- | | Solr | 8.11.1+ or 9.8.1+ | | Elasticsearch | 7.16.2+ or 8.19+ | If you see a "+" next to the product version, it indicates a recommended version or higher within the same major release. For example, "1.18+" means any 1.x version equal to or higher than 1.18, but not 2.x. **Ibexa DXP v4.6** | Name | Version | | ------------- | ----------------------- | | Solr | 7.7+, 8.11.1+ or 9.8.1+ | | Elasticsearch | 7.16.2+ or 8.19+ | If you see a "+" next to the product version, it indicates a recommended version or higher within the same major release. For example, "1.18+" means any 1.x version equal to or higher than 1.18, but not 2.x. **Ibexa DXP v3.3** | Name | Version | | ------------- | ------------------ | | Solr | 7.7 LTS or 8.11.1+ | | Elasticsearch | 7.16.2+ | If you see a "+" next to the product version, it indicates a recommended version or higher within the same major release. For example, "1.18+" means any 1.x version equal to or higher than 1.18, but not 2.x. - For BinaryFile field indexing, Apache Tika 1.20 or higher 1.x version, recommended 1.28.1 or higher. - The above solutions require Oracle Java/Open JDK. The minimum requirement is 8 LTS, recommended 11 LTS. Newer versions aren't supported. ## Graphic Handler **Ibexa DXP v5.0** - GraphicsMagick - ImageMagick - GD Optionally, if you intend to edit [PNG, SVG, GIF or WEBP files in the Image Editor](https://doc.ibexa.co/en/latest/content_management/images/images/#image-optimization), or use it with image variations: - JpegOptim - Optipng - Pngquant 2 - SVGO 1 - Gifsicle - cwebp **Ibexa DXP v4.6** - GraphicsMagick - ImageMagick - GD Optionally if you intend to edit [PNG, SVG, GIF or WEBP files in the Image Editor](https://doc.ibexa.co/en/latest/content_management/images/images/#image-optimization), or use it with image variations: - JpegOptim - Optipng - Pngquant 2 - SVGO 1 - Gifsicle - cwebp **Ibexa DXP v3.3** - GraphicsMagick - ImageMagick - GD Optionally if you intend to edit [PNG, SVG, GIF or WEBP files in the Image Editor](https://doc.ibexa.co/en/latest/content_management/images/images/#image-optimization), or use it with image variations: - JpegOptim - Optipng - Pngquant 2 - SVGO 1 - Gifsicle - cwebp ## [Clustering](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/clustering/clustering/index.md) **Ibexa DXP v5.0** - Linux NFS or S3/EFS (for IO, aka binary files stored in content repository, not supported with legacy) - Redis 7.2+, 8.4+, or Valkey 9.0+ (separate instances for session and cache, both using a `volatile-*` [eviction policy](https://redis.io/docs/latest/develop/reference/eviction/), session instance configured for persistence) - [Varnish](http://varnish-cache.org/) 6.0LTS or 7.1 with [varnish-modules](https://github.com/varnish/varnish-modules/blob/master/README.md) or [Fastly](https://www.fastly.com/) using [the provided bundle](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/http_cache/http_cache/index.md) (for HTTP Cache) If you see a "+" next to the product version, it indicates a recommended version or higher within the same major release. For example, "1.18+" means any 1.x version equal to or higher than 1.18, but not 2.x. **Ibexa DXP v4.6** - Linux NFS or S3/EFS (for IO, aka binary files stored in content repository, not supported with legacy) - Redis 4.0+, 5.0+, 7.2+, 8.4+, or Valkey 9.0+ (separate instances for session and cache, both using a `volatile-*` [eviction policy](https://redis.io/docs/latest/develop/reference/eviction/), session instance configured for persistence), or [Memcached](https://memcached.org/) 1.5 or higher - [Varnish](http://varnish-cache.org/) 6.0LTS or 7.1 with [varnish-modules](https://github.com/varnish/varnish-modules/blob/master/README.md) or [Fastly](https://www.fastly.com/) using [the provided bundle](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/http_cache/http_cache/index.md) (for HTTP Cache) If you see a "+" next to the product version, it indicates a recommended version or higher within the same major release. For example, "1.18+" means any 1.x version equal to or higher than 1.18, but not 2.x. **Ibexa DXP v3.3** - Linux NFS or S3/EFS (for IO, aka binary files stored in content repository, not supported with legacy) - Redis 4.0+ or 5.0+ (separate instances for session and cache, both using a `volatile-*` [eviction policy](https://redis.io/docs/latest/develop/reference/eviction/), session instance configured for persistence) or [Memcached](https://memcached.org/) 1.5 or higher - [Varnish](http://varnish-cache.org/) 6.0LTS with [varnish-modules](https://github.com/varnish/varnish-modules/blob/master/README.md) or [Fastly](https://www.fastly.com/) using [the provided bundle](https://doc.ibexa.co/en/3.3/guide/cache/http_cache/) (for HTTP Cache) If you see a "+" next to the product version, it indicates a recommended version or higher within the same major release. For example, "1.18+" means any 1.x version equal to or higher than 1.18, but not 2.x. ## Filesystem **Ibexa DXP v5.0** - Linux ext4 / XFS **Ibexa DXP v4.6** - Linux ext4 / XFS **Ibexa DXP v3.3** - Linux ext4 / XFS ## Package manager **Ibexa DXP v5.0** - Composer: recent 2.8 version **Ibexa DXP v4.6** - Composer: recent 2.7 version **Ibexa DXP v3.3** - Composer: recent 2.1 version ## Asset manager **Ibexa DXP v5.0** - `Node.js` 22+ - `yarn` 1.15.2+ If you see a "+" next to the product version, it indicates a recommended version or higher within the same major release. For example, "1.18+" means any 1.x version equal to or higher than 1.18, but not 2.x. **Ibexa DXP v4.6** - `Node.js` 18+, 20+, 22+ - `yarn` 1.15.2+ If you see a "+" next to the product version, it indicates a recommended version or higher within the same major release. For example, "1.18+" means any 1.x version equal to or higher than 1.18, but not 2.x. **Ibexa DXP v3.3** - `Node.js` 14+, 16+, 18+ (`Node.js` 14+ has reached its End of Life. We strongly recommend using a newer version to ensure you receive security updates.) - `yarn` 1.15.2+ If you see a "+" next to the product version, it indicates a recommended version or higher within the same major release. For example, "1.18+" means any 1.x version equal to or higher than 1.18, but not 2.x. ## Browser **Ibexa DXP v5.0** Ibexa DXP is developed to work with *any* web browser that supports modern standards, on *any* screen resolution suitable for web, running on *any* device. However, for the Editorial and Administration User Interfaces, you need: a minimum of 1366-by-768 screen resolution, a desktop or tablet device, and a recommended/supported browser among the ones found below. - Mozilla® Firefox® most recent stable version (recommended) - Google Chrome™ most recent stable version (recommended) - Chromium™ based browsers such as Microsoft® Edge® and Opera®, most recent stable version, desktop *and* tablet - Apple® Safari® most recent stable version, desktop *and* tablet **Ibexa DXP v4.6** Ibexa DXP is developed to work with *any* web browser that supports modern standards, on *any* screen resolution suitable for web, running on *any* device. However for the Editorial and Administration User Interfaces you need: a minimum of 1366-by-768 screen resolution, a desktop or tablet device, and a recommended/supported browser among the ones found below. - Mozilla® Firefox® most recent stable version (recommended) - Google Chrome™ most recent stable version (recommended) - Chromium™ based browsers such as Microsoft® Edge® and Opera®, most recent stable version, desktop *and* tablet - Apple® Safari® most recent stable version, desktop *and* tablet **Ibexa DXP v3.3** Ibexa DXP is developed to work with *any* web browser that supports modern standards, on *any* screen resolution suitable for web, running on *any* device. However for the Editorial and Administration User Interfaces you need: a minimum of 1366-by-768 screen resolution, a desktop or tablet device, and a recommended/supported browser among the ones found below. - Mozilla® Firefox® most recent stable version (recommended) - Google Chrome™ most recent stable version (recommended) - Chromium™ based browsers such as Microsoft® Edge® and Opera®, most recent stable version, desktop *and* tablet - Apple® Safari® most recent stable version, desktop *and* tablet ## Ibexa Cloud requirements and setup **Ibexa DXP v5.0** ### Cloud hosting with Ibexa Cloud and Upsun In general, Ibexa Cloud supports all features and services of [Upsun](https://fixed.docs.upsun.com/add-services.html#available-services) that are compatible and supported by the Ibexa DXP version you use. For example: - Upsun provides Redis support for versions 7.2, 7.0, and 6.2. Ibexa DXP supports Redis version 7.2. As a result, Redis is supported on Ibexa Cloud in versions 7.2. Features or services supported by Ibexa DXP but not covered by Upsun may be possible by means of a [custom integration](#custom-integrations). ### Ibexa Cloud Setup support matrix All Ibexa DXP features are supported in accordance with the example above. > **Note: Note** > > As Upsun doesn't support a configuration with multiple PostgreSQL databases, for Ibexa Cloud / Upsun it's impossible to have a DFS table in a separate database. ### Recommended Ibexa Cloud setup For more details on recommended setup configuration see bundled `.platform.app.yaml` and `.platform/` configuration files. These files are kept up-to-date with latest recommendations and can be improved through contributions. ### Supported Ibexa Cloud setup Because of the large range of possible configurations of Ibexa DXP, there are many possibilities beyond what is provided in the default recommended configuration. Make sure to set aside time and budget for: - Verifying your requirements and ensuring they're supported by Upsun - Additional time for adaptation and configuration work, and testing by your development team - Additional consulting/onboarding time with Upsun, Ibexa technical services, and/or one of the many partners with prior experience in using Upsun with Ibexa DXP The cost and effort of this isn't included in Ibexa Cloud subscription and is vary depending on the project. ### Custom integrations Features supported by Ibexa DXP, but not natively by Upsun, can in many cases be used by means of custom integrations with external services. For example, you can create an integration with S3 by means of setting up your own S3 bucket and configuring the relevant parts of Ibexa DXP. We recommend giving the development team working on the project access to the bucket to ensure work is done in a DevOps way without depending on external teams when changes are needed. **Ibexa DXP v4.6** ### Cloud hosting with Ibexa Cloud and Upsun In general, Ibexa Cloud supports all features and services of [Upsun](https://fixed.docs.upsun.com/add-services.html#available-services) that are compatible and supported by the Ibexa DXP version you use. For example: - Upsun provides Redis support for versions 7.2, 7.0, and 6.2. Ibexa DXP supports Redis in versions 4.0, 5.0, and 7.2. As a result, Redis is supported on Ibexa Cloud in version 7.2. Features or services supported by Ibexa DXP but not covered by Upsun may be possible by means of a [custom integration](#custom-integrations). ### Ibexa Cloud Setup support matrix All Ibexa DXP features are supported in accordance with the example above. For example: As Legacy Bridge isn't supported with v3, it's not supported on Ibexa Cloud either. > **Note: Note** > > As Upsun doesn't support a configuration with multiple PostgreSQL databases, for Ibexa Cloud / Upsun it's impossible to have a DFS table in a separate database. ### Recommended Ibexa Cloud setup For more details on recommended setup configuration see bundled `.platform.app.yaml` and `.platform/` configuration files. These files are kept up-to-date with latest recommendations and can be improved through contributions. ### Supported Ibexa Cloud setup Because of the large range of possible configurations of Ibexa DXP, there are many possibilities beyond what is provided in the default recommended configuration. Make sure to set aside time and budget for: - Verifying your requirements and ensuring they're supported by Upsun - Additional time for adaptation and configuration work, and testing by your development team - Additional consulting/onboarding time with Upsun, Ibexa technical services, and/or one of the many partners with prior experience in using Upsun with Ibexa DXP The cost and effort of this isn't included in Ibexa Cloud subscription and is vary depending on the project. ### Custom integrations Features supported by Ibexa DXP, but not natively by Upsun, can in many cases be used by means of custom integrations with external services. For example, you can create an integration with S3 by means of setting up your own S3 bucket and configuring the relevant parts of Ibexa DXP. We recommend giving the development team working on the project access to the bucket to ensure work is done in a DevOps way without depending on external teams when changes are needed. **Ibexa DXP v3.3** ### Cloud hosting with Ibexa Cloud and Upsun In general, Ibexa Cloud supports all features and services of [Upsun](https://fixed.docs.upsun.com/add-services.html#available-services) that are compatible and supported by the Ibexa DXP version you use. For example: - Upsun provides Redis support for versions 3.2, 4.0 and 5.0. Ibexa DXP supports Redis version 4.0 or higher, and recommends 5.0. As a result, Redis is supported on Ibexa Cloud in versions 4.0 and 5.0, but 5.0 is recommended. Features or services supported by Ibexa DXP but not covered by Upsun may be possible by means of a [custom integration](#custom-integrations_1). ### Ibexa Cloud Setup support matrix All Ibexa DXP features are supported in accordance with the example above. For example: As Legacy Bridge isn't supported with v3, it's not supported on Ibexa Cloud either. > **Note: Note** > > As Upsun doesn't support a configuration with multiple PostgreSQL databases, for Ibexa Cloud / Upsun it's impossible to have a DFS table in a separate database. ### Recommended Ibexa Cloud setup For more details on recommended setup configuration see bundled `.platform.app.yaml` and `.platform/` configuration files. These files are kept up-to-date with latest recommendations and can be improved through contributions. ### Supported Ibexa Cloud setup Because of the large range of possible configurations of Ibexa DXP, there are many possibilities beyond what is provided in the default recommended configuration. Make sure to set aside time and budget for: - Verifying your requirements and ensuring they're supported by Upsun - Additional time for adaptation and configuration work, and testing by your development team - Additional consulting/onboarding time with Upsun, Ibexa technical services, and/or one of the many partners with prior experience in using Upsun with Ibexa DXP The cost and effort of this isn't included in Ibexa Cloud subscription and is vary depending on the project. ### Custom integrations Features supported by Ibexa DXP, but not natively by Upsun, can in many cases be used by means of custom integrations with external services. For example, you can create an integration with S3 by means of setting up your own S3 bucket and configuring the relevant parts of Ibexa DXP. We recommend giving the development team working on the project access to the bucket to ensure work is done in a DevOps way without depending on external teams when changes are needed. # Install Ibexa DXP > **Note: Note** > > Installation for production is only supported on Linux. > > To install Ibexa DXP for development on macOS or Windows, see the [installation guide for macOS and Windows](https://doc.ibexa.co/en/latest/getting_started/install_on_mac_os_and_windows/index.md). > **Note: Installing Ibexa OSS** > > This installation guide shows in details how to install Ibexa DXP for users who have a subscription agreement with Ibexa. If you want to install Ibexa OSS, you don't need authentication tokens or an account on updates.ibexa.co, but must adapt the steps shown here to the product edition and the `ibexa/oss-skeleton` repository. ## Prepare work environment To install Ibexa DXP you need a stack with your operating system, MySQL or MariaDB, and PHP. You can install it by following your favorite tutorial, for example: [Install LAMP stack on Ubuntu](https://www.digitalocean.com/community/tutorials/how-to-install-lamp-stack-on-ubuntu). Additional requirements: - [Node.js](https://nodejs.org/en) and [Yarn](https://classic.yarnpkg.com/en/docs/install/#debian-stable) for asset management - `git` for version control For production, you need to [configure an HTTP server](#configure-an-http-server), Apache or nginx (Apache is used as an example below). Before getting started, make sure you review other [requirements](https://doc.ibexa.co/en/latest/getting_started/requirements/index.md) to see the systems that is supported and used for testing. ### Get Composer Install a recent stable version of Composer, the PHP command line dependency manager. Use the package manager for your Linux distribution. For example, on Ubuntu: ``` apt-get install composer ``` To verify that you have the most recent stable version of Composer, you can run: ``` composer -V ``` > **Tip: Install Composer locally** > > If you want to install Composer inside your project root directory only, follow the instructions for [installing Composer in the current directory](https://getcomposer.org/download/). > > If you do so, you must replace `composer` with `php -d memory_limit=-1 composer.phar` in all commands below. ## Install Ibexa DXP ### Set up authentication tokens Ibexa DXP subscribers have access to commercial packages at [updates.ibexa.co](https://updates.ibexa.co/). The site is password-protected. You must set up authentication tokens to access the site. Log in to your Service portal on [support.ibexa.co](https://support.ibexa.co/), go to your **Service Portal**, and look for the following on the **Maintenance and Support agreement details** screen: *[Image: Authentication token]* 1. Select **Create token** (this requires the **Portal administrator** access level). 2. Fill in a label describing the use of the token. This allows you to revoke access later. 3. Save the password, **you aren't able to access it again**. > **Tip: Save the authentication token inauth.jsonto avoid re-typing it** > > Composer asks whether you want to save the token every time you perform an update. If you prefer, you can decline and create an `auth.json` file globally in [`COMPOSER_HOME`](https://getcomposer.org/doc/03-cli.md#composer-home) directory for machine-wide use: > > ``` > composer config --global http-basic.updates.ibexa.co > ``` > > To store your credentials per project, add the credentials to the `COMPOSER_AUTH` variable: > > ``` > export COMPOSER_AUTH='{"http-basic":{"updates.ibexa.co": {"username": "", "password": ""}}}' > ``` > > You then need to [add the contents of this variable to `auth.json`](#authentication-token). > **Tip: Different tokens for different projects on a single host** > > If you configure several projects on one machine, make sure that you set different tokens for each of the projects in their respective `auth.json` files. After this, when running Composer to get updates, you're asked for a username and password. Use: - as username - your Installation key found on the **Maintenance and Support agreement details** page in the Service portal - as password - the token password you retrieved in step 3 above > **Note: Authentication token validation delay** > > You can encounter some delay between creating the token and being able to use it in Composer. It might take up to 15 minutes. > **Caution: Support agreement expiry** > > If your Support agreement expires, your authentication token(s) will no longer work. They will become active again if the agreement is renewed, but this process may take up to 24 hours. *(If the agreement is renewed before the expiry date, there will be no disruption of service.)* If Composer asks for your GitHub token, you must log in to your GitHub account and generate a new token (edit your profile and go to **Developer settings** > **Personal access tokens** > **Generate new token** with default settings). This operation is performed only once, when you install Ibexa DXP for the first time. ### Create project To use Composer to instantly create a project in the current folder with all the dependencies, run the following command: **Ibexa Headless** ``` composer create-project ibexa/headless-skeleton . ``` **Ibexa Experience** ``` composer create-project ibexa/experience-skeleton . ``` **Ibexa Commerce** ``` composer create-project ibexa/commerce-skeleton . ``` Using PHP versions other than 8.3 If you aren't using PHP 8.3 but are using PHP 8.4, PHP 8.2, or any older version, use a different set of commands: **Ibexa Headless** ``` composer create-project ibexa/headless-skeleton --no-install . composer update ``` **Ibexa Experience** ``` composer create-project ibexa/experience-skeleton --no-install . composer update ``` **Ibexa Commerce** ``` composer create-project ibexa/commerce-skeleton --no-install . composer update ``` > **Tip: Authentication token** > > If you added credentials to the `COMPOSER_AUTH` variable, at this point add this variable to `auth.json` (for example, by running `echo $COMPOSER_AUTH > auth.json`). > **Tip: Tip** > > You can set [different version constraints](https://getcomposer.org/doc/articles/versions.md), for example, specific tag (`5.0.7`), version range (`~5.0.1`), or stability (`^5.0@rc`): > > ``` > composer create-project ibexa/experience-skeleton:5.0.7 . > ``` > **Note: Ibexa Cloud** > > If you're deploying your installation on [Upsun](https://fixed.docs.upsun.com/guides/ibexa/deploy.html), run the following commands: > > ``` > composer require ibexa/cloud > php bin/console ibexa:cloud:setup --upsun > ``` > > These commands add the necessary package and provide the required configuration for using Upsun. For more information, see [Install on Ibexa Cloud](https://doc.ibexa.co/en/latest/ibexa_cloud/install_on_ibexa_cloud/index.md). #### Add project to version control It's recommended to add your project to version control. Initiate your project repository: ``` git init; git add . > /dev/null; git commit -m "init" > /dev/null ``` ### Change installation parameters At this point configure your database via the `DATABASE_URL` in the `.env` file, depending of the database you're using: `DATABASE_URL=mysql://user:password@host:port/database_name`. or `DATABASE_URL=postgresql://user:password@host:port/database_name`. > **Tip: Encoding database password** > > The password entered in `DATABASE_URL` must either be URL encoded, or not contain any special characters that would require URL encoding. > > For more information, see [Encoding database password](https://doc.ibexa.co/en/latest/getting_started/troubleshooting/#encoding-database-password). ### Add entropy to improve cryptographic randomness Choose a [secret](https://symfony.com/doc/7.4/reference/configuration/framework.html#secret) and provide it in the `APP_SECRET` parameter in `.env`. It should be a random string, made up of at least 32 characters, numbers, and symbols. It's used by Symfony when generating [CSRF tokens](https://symfony.com/doc/7.4/security/csrf.html), [encrypting cookies](https://symfony.com/doc/7.4/security/remember_me.html), and for creating signed URIs when using [ESI (Edge Side Includes)](https://symfony.com/doc/7.4/http_cache/esi.html). > **Caution: Caution** > > The app secret is crucial to the security of your installation. Be careful about how you generate it, and how you store it. Here's one way to generate a 64 characters long, secure random string as your secret, from command line: > > ``` > php -r "print bin2hex(random_bytes(32));" > ``` > > Don't commit the secret to version control systems, or share it with anyone who doesn't strictly need it. If you have any suspicion that the secret may have been exposed, replace it with a new one. The same goes for other secrets, like database password, Varnish invalidate token, JWT passphrase, and more. > > After changing the app secret, make sure that you clear the application cache and log out all the users. > > For more information, see [Symfony documentation](https://symfony.com/doc/7.4/reference/configuration/framework.html#secret). > > It's recommended to store the database credentials in your `.env.local` file and not commit it to the Version Control System. In `DATABASE_VERSION` you can also configure the database server version (for a MariaDB database, prefix the value with `mariadb-`). > **Tip: Using PostgreSQL** > > If you want an installation with PostgreSQL instead of MySQL or MariaDB, refer to [Using PostgreSQL](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/databases/#using-postgresql). #### Install and configure a search engine You may choose to replace the [default search engine](https://doc.ibexa.co/en/latest/search/search_engines/legacy_search_engine/legacy_search_overview/index.md) with either Solr or Elasticsearch. **Solr** Follow [How to set up Solr search engine](https://doc.ibexa.co/en/latest/search/search_engines/solr_search_engine/install_solr/index.md) to install Solr. **Elasticsearch** Do the following steps to enable Elasticsearch: 1. [Download and install Elasticsearch](https://doc.ibexa.co/en/latest/search/search_engines/elasticsearch/install_elasticsearch/index.md) 2. [Verify that the Elasticsearch instance is up](https://doc.ibexa.co/en/latest/search/search_engines/elasticsearch/install_elasticsearch/#verify-the-instance) 3. [Set the default search engine](https://doc.ibexa.co/en/latest/search/search_engines/elasticsearch/install_elasticsearch/#set-the-default-search-engine) 4. [Configure the search engine](https://doc.ibexa.co/en/latest/search/search_engines/elasticsearch/configure_elasticsearch/index.md) 5. [Push the templates](https://doc.ibexa.co/en/latest/search/search_engines/elasticsearch/install_elasticsearch/#push-the-templates) Configure the following parameter in the `.env` file: ``` ELASTICSEARCH_DSN=http://localhost:9200 ``` ### Create a database Install Ibexa DXP and create a database with: ``` php bin/console ibexa:install ``` Before executing the command make sure that the database user has sufficient permissions. The installer will prompt you for a new password for the `admin` user. Make sure to use a [strong password](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/security/security_checklist/#strong-passwords) meeting all the default [password rules](https://doc.ibexa.co/en/latest/users/passwords/#password-rules): - a minimum length of 10 characters - at least one upper case letter - at least one number > **Note: Note** > > In scenarios where entering the new password isn't possible, for example, in automated deployments and Continuous Integration environments, use the `--no-interaction` option to skip changing the password and keep the default one, `publish`: > > ``` > php bin/console ibexa:install --no-interaction > ``` > > If doing so, [modify the password for the `admin` user](https://doc.ibexa.co/en/latest/users/update_basic_user_data/#change-password) before [going live with your project](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/security/security_checklist/index.md). ### Run post-installation script Run the post-installation script with the following command: ``` composer run post-install-cmd ``` ## Use PHP's built-in server For development you can use the built-in PHP server. ``` php -S 127.0.0.1:8000 -t public ``` Your PHP web server is accessible at `http://127.0.0.1:8000` You can also use [Symfony CLI](https://symfony.com/download): ``` symfony serve ``` ## Prepare installation for development Consider adding the Symfony DebugBundle which fixes memory outage when dumping objects with circular references. The DebugBundle contains the [VarDumper](https://symfony.com/doc/7.4/components/var_dumper.html) and [its Twig integration](https://symfony.com/doc/7.4/components/var_dumper.html#debugbundle-and-twig-integration). ``` composer require --dev symfony/debug-bundle ``` For detailed information about request treatment, you can also install [Symfony Profiler](https://symfony.com/doc/7.4/profiler.html): ``` composer require --dev symfony/profiler-pack ``` To get both features in one go use: ``` composer require --dev symfony/debug-pack ``` ## Configure an HTTP server To use Ibexa DXP with an HTTP server, you need to [set up directory permissions](#set-up-permissions) and [prepare a virtual host](#set-up-virtual-host). ### Set up permissions For development needs, the web user can be made the owner of all your files (for example with the `www-data` web user): ``` chown -R www-data:www-data ``` Directories `var` and `public/var` must be writable by CLI and the web server user. Future files and directories created by these two users need to inherit those permissions. > **Caution: Caution** > > For security reasons, in production, the web server cannot have write access to other directories than `var`. Skip the step above and follow the link below for production needs instead. > > You must also make sure that the web server cannot interpret the files in the `var` directory through PHP. To do so, follow the instructions on [setting up a virtual host below](#set-up-virtual-host). To set up permissions for production, it's recommended to use an ACL (Access Control List). See [Setting up or Fixing File Permissions](https://symfony.com/doc/7.4/setup/file_permissions.html) in Symfony documentation for information on how to do it on different systems. ### Set up virtual host Prepare a [virtual host configuration](https://en.wikipedia.org/wiki/Virtual_hosting) for your site. **Apache** You can copy [the example vhost file](https://raw.githubusercontent.com/ibexa/post-install/main/resources/templates/apache2/vhost.template) to `/etc/apache2/sites-available` as a `.conf` file and modify it to fit your project. Specify `//public` as the `DocumentRoot` and `Directory`, or ensure `BASEDIR` is set in the environment. Uncomment the line that starts with `#if [APP_ENV]` and set the value to `prod` or `dev`, depending on the environment that you're configuring, or ensure `APP_ENV` is set in the environment. ``` SetEnvIf Request_URI ".*" APP_ENV=prod ``` When the virtual host file is ready, enable the virtual host and disable the default: ``` a2ensite ibexa a2dissite 000-default.conf ``` Finally, restart the Apache server. The command may vary depending on your Linux distribution. For example, on Ubuntu use: ``` service apache2 restart ``` **nginx** You can use [this example vhost file](https://raw.githubusercontent.com/ibexa/post-install/main/resources/templates/nginx/vhost.template) and modify it to fit your project. You also need the `ibexa_params.d` files that should reside in a subdirectory below where the main file is, [as is shown here](https://github.com/ibexa/post-install/tree/main/resources/templates/nginx). Specify `//public` as the `root`, or ensure `BASEDIR` is set in the environment. Ensure `APP_ENV` is set to `prod` or `dev` in the environment, depending on the environment that you're configuring, and uncomment the line that starts with `#if[APP_ENV`. When the virtual host file is ready, enable the virtual host and disable the default. Finally, restart the nginx server. The command may vary depending on your Linux distribution. Open your project in the browser by visiting the domain address, for example `http://localhost:8080`. You should see the welcome page. ## Post-installation steps > **Note: Security checklist** > > See the [Security checklist](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/security/security_checklist/index.md) for a list of security-related issues you should take care of before going live with a project. ### Enable Date-based Publisher To enable delayed publishing of Content using the Date-based Publisher, you must set up cron to run the `bin/console ibexa:scheduled:run` command periodically. For example, to check for publishing every minute, add the following script: `echo '* * * * * cd [path-to-ibexa-dxp]; php bin/console ibexa:cron:run --quiet --env=prod' > ezp_cron.txt` For 5-minute intervals: `echo '*/5 * * * * cd [path-to-ibexa-dxp]; php bin/console ibexa:cron:run --quiet --env=prod' > ezp_cron.txt` Next, append the new cron to user's crontab without destroying existing crons. Assuming the web server user data is `www-data`: `crontab -u www-data -l|cat - ezp_cron.txt | crontab -u www-data -` Finally, remove the temporary file: `rm ezp_cron.txt` ### Enable the Link manager To make use of the [Link Manager](https://doc.ibexa.co/en/latest/content_management/url_management/url_management/#enabling-automatic-url-validation). ### Enable discount re-indexing (Commerce) Enable [discount re-indexing in the background](https://doc.ibexa.co/en/latest/discounts/configure_discounts/#discount-re-indexing). ## Ibexa Cloud If you want to host your application on Ibexa Cloud, follow the [Ibexa Cloud](https://doc.ibexa.co/en/latest/ibexa_cloud/install_on_ibexa_cloud/index.md) procedure. # Install Ibexa DXP on macOS or Windows This page explains how to install Ibexa DXP on macOS or Windows. > **Caution: Caution** > > This procedure is **for development purposes only**. Installing Ibexa DXP for production purposes is supported only on Linux. > > For information about installing the product on Linux, see [Install Ibexa DXP](https://doc.ibexa.co/en/latest/getting_started/install_ibexa_dxp/index.md). ### Prepare work environment To install Ibexa DXP, you need a stack with MySQL and PHP. Additionally, you need [Node.js](https://nodejs.org/en) and [Yarn](https://classic.yarnpkg.com/en/docs/install/) for asset management. If you want to use a web server, you need to install it as well: - For Windows: Apache - For macOS: Apache/nginx The instructions below assume that you're using Apache. Windows Locate the `php.ini` file and open it in a text editor. Provide the missing values to relevant parameters, for example, `date.timezone` and `memory_limit`: ``` date.timezone = "Europe/Warsaw" memory_limit = 4G ``` Uncomment or add extensions relevant to your project, for example, `opcache` extension for PHP (recommended, not required): ``` zend_extension=opcache.so ``` You can install Apache as a Windows service by running the following command in CMD as administrator: ``` httpd.exe -k -install ``` You can then start it with: ``` httpd.exe -k start ``` Edit Apache configuration file `httpd.conf`. Replace placeholder values with corresponding values from your project, for example, `ServerName localhost:80`. Uncomment relevant modules, for example: ``` LoadModule rewrite_module modules/mod_rewrite.so LoadModule vhost_alias_module libexec/apache2/mod_vhost_alias.so ``` ## Get Composer **macOS** Install Composer using a package manager, for example, [Homebrew](https://brew.sh/). **Windows** Download and run [Composer-Setup.exe](https://getcomposer.org/Composer-Setup.exe) - it installs the latest Composer version. ## Install Ibexa DXP At this point the installation procedure is the same as when installing on Linux. Follow the steps from the main [Install Ibexa DXP](https://doc.ibexa.co/en/latest/getting_started/install_ibexa_dxp/#install-ibexa-dxp) page. ## Set up virtual host Prepare a [virtual host configuration](https://httpd.apache.org/docs/2.4/vhosts/) for your site in your Apache directory: - For Windows: `\conf\vhosts` - For macOS: `/private/etc/apache2/users/` Then restart the Apache server. ## Set up permissions Directories `var` and `web/var` need to be writable by CLI and web server user. Future files and directories created by these two users need to inherit those permissions. For more information, see [Setting up or Fixing File Permissions](https://symfony.com/doc/7.4/setup/file_permissions.html). > **Note: Security checklist** > > See the [Security checklist](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/security/security_checklist/index.md) for a list of security-related issues that you should take care of before going live with a project. # Install with DDEV This guide provides a step-by-step walkthrough of installing Ibexa DXP by using [DDEV](https://ddev.com/). DDEV is an open-source tool that simplifies the process of setting up local PHP development environments. ## Requirements Before you start the installation, ensure that you have the following software installed: - [Docker](https://docs.docker.com/get-started/get-docker/) - [DDEV](https://ddev.readthedocs.io/en/latest/users/install/ddev-installation/) ## Installation ### 1. Create a DDEV project directory Start by creating a new directory for your DDEV project by using the following command, where `` stands for your desired directory name: ``` mkdir my-ddev-project && cd my-ddev-project ``` ### 2. Configure DDEV #### Configure PHP version and document root Next, configure your DDEV environment with the following command: ``` ddev config --project-type=php --php-version 8.4 --nodejs-version 22 --docroot=public ``` This command sets the project type to PHP, the PHP version to 8.4, the document root to `public` directory, and creates the document root if it doesn't exist. #### Use another database type (optional) By default, DDEV uses MariaDB. To use PostgreSQL instead, run the following command: ``` ddev config --database=postgres:14 ``` To use MySQL instead, run the following command: ``` ddev config --database=mysql:8.4 ``` You can also use other versions of MariaDB, MySQL or PostgreSQL. See [DDEV database types documentation](https://ddev.readthedocs.io/en/latest/users/extend/database-types/) for available version ranges. #### Configure database connection Now, configure the database connection for your Ibexa DXP project. Depending on your database of choice (MySQL or PostgreSQL), use the appropriate command below. > **Note: Note** > > Those commands set a `DATABASE_URL` environment variable inside the container which overrides [the variable from `.env`](https://doc.ibexa.co/en/latest/getting_started/install_ibexa_dxp/#change-installation-parameters). > > To use `.env.local` file instead of server-level environment variables, see [Using dotenv](#using-dotenv). **MariaDB / MySQL** ``` ddev config --web-environment-add DATABASE_URL=mysql://db:db@db:3306/db ``` To ensure consistent character set when performing operations both in Symfony context and with the `ddev mysql` client add the following database server configuration. Create the file `.ddev/mysql/utf8mb4.cnf` with the following content: ``` [mysqld] character-set-server = utf8mb4 collation-server = utf8mb4_unicode_520_ci ``` **PostgreSQL** ``` ddev config --web-environment-add DATABASE_URL=postgresql://db:db@db:5432/db ``` #### Enable Mutagen (optional) If you're using macOS or Windows, you might want to enable [Mutagen](https://ddev.readthedocs.io/en/latest/users/install/performance/#mutagen) to improve performance. You can do this by running the following command: ``` ddev config --performance-mode=mutagen ``` See [DDEV performance documentation](https://ddev.readthedocs.io/en/latest/users/install/performance/) for more. #### Change port mapping (optional) By default, DDEV uses ports 80 and 443. You can [set different ports](https://ddev.readthedocs.io/en/latest/users/usage/troubleshooting/#method-2-fix-port-conflicts-by-configuring-your-project-to-use-different-ports) with a command like the following: ``` ddev config --router-http-port=8080 --router-https-port=8443 ``` ### 3. Start DDEV Proceed by starting your DDEV environment with the following command: ``` ddev start ``` > **Tip: Tip** > > If you forgot some part of configuration, you can set it by using `ddev config` later, but afterwards you have to restart DDEV with `ddev restart`. ### 4. Composer authentication Next, you need to [set up authentication tokens](https://doc.ibexa.co/en/latest/getting_started/install_ibexa_dxp/#set-up-authentication-tokens) by modifying the Composer configuration. You must run the following command **after** executing `ddev start`, because the command runs inside the container. Replace `` and `` with your actual installation key and token password. ``` ddev composer config --global http-basic.updates.ibexa.co ``` This authentication doesn't persist if the project is restarted (by `ddev restart` or `ddev composer create-project`). You can back up the authentication file (`auth.json`) by using the following command: ``` ddev exec "mkdir -p .ddev/homeadditions/.composer; cp ~/.composer/auth.json .ddev/homeadditions/.composer" ``` If you want to reuse an existing `auth.json` file, see [Using `auth.json`](#using-authjson). ### 5. Create project Once DDEV is running, use Composer to create a new Ibexa DXP project. Remember to replace `` and `` with your desired edition and version. ``` ddev composer create-project ibexa/-skeleton: ``` > **Tip: Tip** > > Consider adding the Symfony Debug bundle which fixes memory outage when dumping objects with circular references. `ddev composer require --dev symfony/debug-bundle` ### 6. Install the platform and its database Once you've made this change, you can proceed to install Ibexa DXP. ``` ddev php bin/console ibexa:install ``` The installer will prompt you for a new password for the `admin` user. The password must meet the following rules: - a minimum length of 10 characters - at least one upper case letter - at least one number You may use the `--no-interaction` option to skip the password question and keep the default `publish`. ### 7. Open browser Once the above steps are completed, open the Ibexa DXP's webpage by running the `ddev launch` command. > **Tip: Tip** > > You can also see the project URL in the `ddev start` output. ### 8. Start using Ibexa DXP You can now start using Ibexa DXP and implement your own website on the platform. You can edit the configuration and code in the DDEV project directory. You can use commands listed in the documentation by prefixing them with `ddev exec` or by opening a terminal inside the container by using `ddev ssh`. For example, if a guideline invites you to run `php bin/console cache:clear`, you can do it in the DDEV container in one of the following ways: - run `ddev php bin/console cache:clear` - enter `ddev ssh` and run `php bin/console cache:clear` after the new prompt ## Other options for configuration DDEV offers several ways to get the same result, offering different levels of flexibility or adaptability to your development environment. > **Tip: Tip** > > Learn more about the [DDEV commands](https://ddev.readthedocs.io/en/latest/users/usage/commands/): > > - run [`ddev --help`](https://ddev.readthedocs.io/en/latest/users/usage/cli/#using-the-ddev-command) to list all commands > - run [`ddev help `](https://ddev.readthedocs.io/en/latest/users/usage/commands/#help) to get usage details about a specific command > > Learn more about DDEV configuration from [`ddev config` command documentation](https://ddev.readthedocs.io/en/latest/users/usage/commands/#config) and [advanced configuration files documentation](https://ddev.readthedocs.io/en/latest/users/configuration/config/). ### Using `auth.json` An `auth.json` file can be used for one project, or globally for all projects, with the [DDEV `homeaddition` feature](https://ddev.readthedocs.io/en/latest/users/extend/in-container-configuration/). For example, you can copy an `auth.json` file to a DDEV project: `cp /auth.json .ddev/homeadditions/.composer` Alternatively, the Composer global `auth.json` can be the DDEV global `auth.json` with the help of a symbolic link: `mkdir -p ~/.ddev/homeadditions/.composer && ln -s ~/.composer/auth.json ~/.ddev/homeadditions/.composer/auth.json` If the DDEV project has already been started, you need to run `ddev restart`. The use of an `auth.json` file replaces step [4. Composer authentication](#4-composer-authentication). ### Using Dotenv Instead of using environment variables inside the container, a [`.env.local`](https://symfony.com/doc/7.4/configuration.html#overriding-environment-values-via-env-local) file can be added to the project. The following example shows the use of `.env.local` with database configuration: - Skip step [2. Configure DDEV / Configure database connection](#configure-database-connection). - Modify step [5. Create Ibexa DXP project](#5-create-project) to insert the database setting: ``` ddev composer create-project ibexa/commerce-skeleton --no-install; echo "DATABASE_URL=mysql://db:db@db:3306/db" >> .env.local; ddev composer install; ``` > **Note: Precedence** > > For the same variable, its server level environment value overrides its application level `.env` value. To switch a variable from `ddev config --web-environment-add` command to `.env.local` file, you have to do either of the following: > > - remove it from under the `web_environment:` key in `.ddev/config.yaml` file then restart the project > - rebuild the project from scratch ### Nginx Server Blocks Even if Ibexa DXP works with the default Nginx configuration that comes with DDEV, it's recommended to use a dedicated one. Copy the Server Blocks template as a new Nginx configuration: ``` cp vendor/ibexa/post-install/resources/templates/nginx/vhost.template .ddev/nginx_full/ibexa.conf cp -r vendor/ibexa/post-install/resources/templates/nginx/ibexa_params.d .ddev/nginx_full/ ``` Then, replace the placeholders with the appropriate values in `.ddev/nginx_full/ibexa.conf`: | Placeholder | Value | | ----------------------- | ---------------------------- | | `%PORT%` | `80` | | `%HOST_LIST%` | `*.ddev.site` | | `%BASEDIR%` | `/var/www/html` | | `%BODY_SIZE_LIMIT_M%` | `0` | | `%TIMEOUT_S%` | `90s` | | `%FASTCGI_PASS%` | `unix:/var/run/php-fpm.sock` | | `%BINARY_DATA_HANDLER%` | empty string | Because of path resolution inside DDEV's Nginx, you must replace one more thing: `ibexa_params.d` with `sites-enabled/ibexa_params.d`. You can, for example, do it with `sed`: ``` sed -i 's/%PORT%/80/' .ddev/nginx_full/ibexa.conf; sed -i 's/%HOST_LIST%/*.ddev.site/' .ddev/nginx_full/ibexa.conf; sed -i 's/%BASEDIR%/\/var\/www\/html/' .ddev/nginx_full/ibexa.conf; sed -i 's/%BODY_SIZE_LIMIT_M%/0/' .ddev/nginx_full/ibexa.conf; sed -i 's/%TIMEOUT_S%/90s/' .ddev/nginx_full/ibexa.conf; sed -i 's/%FASTCGI_PASS%/unix:\/var\/run\/php-fpm.sock/' .ddev/nginx_full/ibexa.conf; sed -i 's/%BINARY_DATA_HANDLER%//' .ddev/nginx_full/ibexa.conf; sed -i 's/ibexa_params.d/sites-enabled\/ibexa_params.d/' .ddev/nginx_full/ibexa.conf; ``` If you want to use HTTPS, you must add the following rule to avoid mixed content (HTTPS pages linking to HTTP resources): `fastcgi_param HTTPS $fcgi_https;` For example, you can append it to Ibexa's FastCGI config: ``` echo 'fastcgi_param HTTPS $fcgi_https;' >> .ddev/nginx_full/ibexa_params.d/ibexa_fastcgi_params ``` ### Switch to Apache and its virtual host To use Apache instead of the default Nginx, run the following command: ``` ddev config --webserver-type=apache-fpm ``` Ibexa DXP can't run on Apache without a dedicated virtual host. To set the Apache virtual host, override `.ddev/apache/apache-site.conf` with Ibexa DXP's config. You can do it manually or by using a script. #### Manual procedure Copy the virtual host template as the new Apache configuration: ``` cp vendor/ibexa/post-install/resources/templates/apache2/vhost.template .ddev/apache/apache-site.conf ``` Then, replace the placeholders with the appropriate values in `.ddev/apache/apache-site.conf`: | Placeholder | Value | | ------------------- | ---------------------------- | | `%IP_ADDRESS%` | `*` | | `%PORT%` | `80` | | `%HOST_NAME%` | `my-ddev-project.ddev.site` | | `%HOST_ALIAS%` | `*.ddev.site` | | `%BASEDIR%` | `/var/www/html` | | `%BODY_SIZE_LIMIT%` | `0` | | `%TIMEOUT%` | `0` | | `%FASTCGI_PASS%` | `unix:/var/run/php-fpm.sock` | You can, for example, do it with `sed`: ``` sed -i 's/%IP_ADDRESS%/*/' .ddev/apache/apache-site.conf sed -i 's/%PORT%/80/' .ddev/apache/apache-site.conf sed -i 's/%BASEDIR%/\/var\/www\/html/' .ddev/apache/apache-site.conf sed -i 's/%BODY_SIZE_LIMIT%/0/' .ddev/apache/apache-site.conf sed -i 's/%TIMEOUT%/0/' .ddev/apache/apache-site.conf sed -i 's/%FASTCGI_PASS%/unix:\/var\/run\/php-fpm.sock/' .ddev/apache/apache-site.conf ``` If you want to use HTTPS, you must add the following rule to avoid mixed content (HTTPS pages linking to HTTP resources): `SetEnvIf X-Forwarded-Proto "https" HTTPS=on` You can, for example, do it with `sed`: ``` sed -i 's/DirectoryIndex index.php/DirectoryIndex index.php\n\n SetEnvIf X-Forwarded-Proto "https" HTTPS=on/' .ddev/apache/apache-site.conf ``` Finally, restart the project: ``` ddev restart ``` #### Scripted procedure Generate the virtual host with [`vhost.sh`](https://github.com/ibexa/docker/blob/main/scripts/vhost.sh): ``` curl -O https://raw.githubusercontent.com/ibexa/docker/main/scripts/vhost.sh bash vhost.sh --template-file=vendor/ibexa/post-install/resources/templates/apache2/vhost.template \ --ip='*' \ --host-name='my-ddev-project.ddev.site' \ --host-alias='*.ddev.site' \ --basedir='/var/www/html' \ --sf-env=dev \ > .ddev/apache/apache-site.conf sed -i 's/php5-fpm.sock/php-fpm.sock/' .ddev/apache/apache-site.conf sed -i 's/DirectoryIndex index.php/DirectoryIndex index.php\n SetEnvIf X-Forwarded-Proto "https" HTTPS=on/' .ddev/apache/apache-site.conf rm vhost.sh ``` Then, restart the project: ``` ddev restart ``` ### Run an already existing project To run an existing project, you need to: 1. Configure the DDEV project. 2. Start the DDEV project. 3. Add Composer authentication. 4. Install dependencies packages with Composer. 5. Populate the contents, which could mean: - getting a clean database with `ddev php bin/console ibexa:install` and adding some data with [Ibexa data migration](https://doc.ibexa.co/en/latest/content_management/data_migration/importing_data/index.md), or - injecting a dump with [`ddev import-db`](https://ddev.readthedocs.io/en/latest/users/usage/commands/#import-db) and copying related binary files into `public/var`. The following examples run an already [version-controlled project](https://doc.ibexa.co/en/latest/getting_started/install_ibexa_dxp/#add-project-to-version-control) and have the right content structure (but no content): ``` # Clone the version-controlled project and enter its local directory git clone my-ddev-project && cd my-ddev-project # Exclude the whole `.ddev/` directory from version control (some DDEV config could have been committed and shared, see notice below) .ddev/ >> .gitignore # Configure the DDEV project then start it ddev config --project-type=php --php-version 8.3 \ --docroot=public \ --web-environment-add DATABASE_URL=mysql://db:db@db:3306/db \ --http-port=8080 --https-port=8443 ddev start # Configure Composer authentication ddev composer config --global http-basic.updates.ibexa.co # Install the dependencies packages ddev composer install # Populate the database with a clean install ddev php bin/console ibexa:install # Add some content types using a migration file (previously created on another installation) and update the GraphQL schema ddev php bin/console ibexa:migrations:migrate --file=project_content_types.yaml # Open the project in the default browser which should display the default SiteAccess frontpage ddev launch ``` Notice that the example adds the whole `.ddev/` directory to `.gitignore`, but you can also version parts of it. Some DDEV configs can be shared among developers. For example, a common `.ddev/config.yaml` can be committed for everyone and [locally extended or overridden](https://ddev.readthedocs.io/en/latest/users/extend/customization-extendibility/#extending-configyaml-with-custom-configyaml-files). Compared to running a clean install like described in [Installation steps](#installation), you can proceed as follows: - In [1. Create a DDEV project directory](#1-create-a-ddev-project-directory), you can use an existing directory that contains an Ibexa DXP project instead of creating an empty directory. - In [5. Create Ibexa DXP project](#5-create-project), use only `ddev composer install` instead of `ddev composer create-project`. - Populate the database with [Ibexa data migration](https://doc.ibexa.co/en/latest/content_management/data_migration/importing_data/index.md) or [`ddev import-db`](https://ddev.readthedocs.io/en/latest/users/usage/commands/#import-db). ### Hostnames and domains If the local project needs to answer to real production domains (for example, to use the existing [hostname to SiteAccess](https://doc.ibexa.co/en/latest/multisite/siteaccess/siteaccess_matching/#maphost) or [hostname element to SiteAccess](https://doc.ibexa.co/en/latest/multisite/siteaccess/siteaccess_matching/#hostelement) mappings), you can use [additional hostnames](https://ddev.readthedocs.io/en/latest/users/extend/additional-hostnames/). > **Caution: Caution** > > As this feature modifies domain resolution, the real website may be unreachable until the `hosts` file is manually cleaned. ### Cluster or Ibexa Cloud You can use DDEV to locally simulate a production cluster. - See [Clustering with DDEV](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/clustering/clustering_with_ddev/index.md) to add Elasticsearch, Solr, or Redis to your DDEV installation. - See [DDEV and Ibexa Cloud](https://doc.ibexa.co/en/latest/ibexa_cloud/ddev_and_ibexa_cloud/index.md) to locally run an Ibexa DXP project by using DDEV. ## Stop or remove the project If you need to stop the project to start it again later, use `ddev stop`. Then, use `ddev start` to run the project in the same state. If you want to fully remove the project: - delete the DDEV elements without backup: `ddev delete --omit-snapshot && rm -rf ./ddev` - remove the project folder: `cd .. && rm -r my-ddev-project` If [additional hostnames](#hostnames-and-domains) have been used, you must clean the hosts file. To learn more about removing all projects at once or DDEV itself, see [Uninstalling DDEV](https://ddev.readthedocs.io/en/latest/users/usage/uninstall/). # First steps This page lists first steps you can take after installing Ibexa DXP. These steps are the most common actions you may need to take in a new installation. > **Tip: Beginner tutorial** > > To go through a full tutorial that leads from a clean installation to creating a full site, see [Beginner tutorial](https://doc.ibexa.co/en/latest/tutorials/beginner_tutorial/beginner_tutorial/index.md). ## Remove welcome page *[Image: Welcome page]* To remove the welcome page and get a completely clean installation to start your project with, remove the following files and folders from your installation: - Delete the file `config/packages/ibexa_welcome_page.yaml` - Delete the `templates/themes/standard/full/welcome_page.html.twig` file - Delete the `assets/scss` folder - Delete all `translations/ibexa_platform_welcome_page.*` files - In `webpack.config.js` remove the `Encore.addEntry` section and uncomment the last line, so that the end of the file looks like this: ``` module.exports = [ibexaConfig, ...customConfigs, projectConfig]; // uncomment this line if you've commented-out the above lines module.exports = [ eZConfig, ibexaConfig, ...customConfigs ]; ``` ## Add a content type 1. In your browser, go to the back office: `/admin`, and log in with the default username: `admin` using the password specified during installation. > **Caution: Password change** > > Make sure that you change the default password before you switch your installation from development to production. For more information about passwords, see [Passwords](https://doc.ibexa.co/en/latest/users/passwords/index.md). For more information about production security, see [Security checklist](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/security/security_checklist/index.md). 2. In the upper-right corner, click the avatar icon and in the drop-down menu disable the [Focus mode](https://doc.ibexa.co/projects/userguide/en/5.0/getting_started/discover_ui/#focus-mode). 1. Select content and go to content types. 1. Enter the content group and create a new content type. *[Image: Creating a content type]* 5. Input the content type's name, for example "Blog Post", and identifier: `blog_post`. 1. Below, add a field definition of the type Text Line. Name it "Title" and give it identifier `title`. 1. Add another field definition: Text (type Rich text) with identifier `text`. 1. Save the content type. For more information, see [Content model](https://doc.ibexa.co/en/latest/content_management/content_model/index.md). ## Create Twig templates and match then with view config To display content in the front page you need to define content views and templates. Content views decide which templates and controllers are used to display content. 1. In `config/packages/ibexa.yaml`, under `ibexa.system` add the following block (pay attention to indentation: `site_group` should be one level below `system`): ``` site_group: content_view: full: blog_post: template: full\blog_post.html.twig match: Identifier\ContentType: [blog_post] ``` Content view templates use the [Twig templating engine](https://twig.symfony.com/). 2. Create a template file `templates/full/blog_post.html.twig`: ```

{{ ibexa_render_field(content, 'title') }}

{{ ibexa_render_field(content, 'text') }}
``` For more information, see [Templates](https://doc.ibexa.co/en/latest/templating/templates/templates/index.md) and [Twig documentation](https://twig.symfony.com/doc/3.x/). ## Create content and test view templates 1. Go to the back office, select **Content** -> **Content structure**, and create a new content item by clicking **Create content**. *[Image: Creating a Blog Post]* 2. Select a Blog Post content type. Fill in the content item and publish it. 1. To preview the new content item on the front page, go to `/`. For example, if the title of the Blog post is "First blog post", the address is `/first-blog-post`. *[Image: Previewing Content]* ## Add SiteAccesses You can use SiteAccesses to serve different versions of the website. SiteAccesses are used depending on matching rules. They're set up in YAML configuration under the `ibexa.siteaccess.list` key. 1. In `config/packages/ibexa.yaml` add a new SiteAccess called `de` for the German version of the website: ``` ibexa: # ... siteaccess: list: [site, de] groups: site_group: [site, de] ``` The SiteAccess is automatically matched based on the last part of the URI. 2. You can now access the front page through the new SiteAccess: `/de`. > **Note: Log in** > > At this point you need to log in to preview the new SiteAccess, because an anonymous visitor doesn't have permissions to view it. See [section about permissions below](#set-up-permissions). For now the new SiteAccess doesn't differ from the main site. For more information, see [Multisite](https://doc.ibexa.co/en/latest/multisite/multisite/index.md) and [SiteAccess matchers](https://doc.ibexa.co/en/latest/multisite/siteaccess/siteaccess_matching/#available-siteaccess-matchers). ## Add a language and translate Content One of the most common use cases for SiteAccesses is having different language versions of a site. 1. To set up the `de` SiteAccess to use a different language, add its configuration under `ibexa.system`, below `site.languages`: ``` site: languages: [eng-GB] de: languages: - ger-DE - eng-GB ``` This means that German is used as the main language for this SiteAccess, and English as a fallback. 2. Go to the back office and select **Admin** > **Languages**. Add a new language called "German", with the language code `ger-DE`. Make sure it's enabled. *[Image: Creating a language]* 3. Next, go to the **Content structure** and open the blog post you had created earlier. Switch to the **Translations** tab and add a new translation. *[Image: Adding a translation]* 4. Select German and base the new translation on the English version. Edit the content item and publish it. 1. Go to the front page. The blog post now displays different content, depending on which SiteAccess you enter it from: `/` or `/de/`. *[Image: Previewing translated Content]* For more information, see [Languages](https://doc.ibexa.co/en/latest/multisite/languages/languages/index.md) and [Set up translation SiteAccess](https://doc.ibexa.co/en/latest/multisite/set_up_translation_siteaccess/index.md). ## Add a design The design engine enables you to use different themes consisting of templates and assets. Each theme is stored in a separate folder and assigned to a SiteAccess. To create a new theme: 1. Add the following configuration at the bottom of `config/packages/ibexa.yaml` (at the same level as `ibexa`): ``` ibexa_design_engine: design_list: site_design: [site_design] de_design: [de_design] ``` 2. In configuration of the `de` SiteAccess (under `ibexa.system.de`) add: `design: de_design` 1. Under `site`, add `design: site_design` 1. Go back to the `content_view` configuration for the blog post. Change the path to the template so that it points to the folder for the correct design: `template: '@ibexadesign\full\blog_post.html.twig'` This means that the app looks for the `blog_post.html.twig` file in a folder relevant for the SiteAccess: `de_design` for the `de` SiteAccess, or `site_design` for other SiteAccesses in `site_group`. 5. Create a `themes` folder under `templates`, and two folders under it: `de_design` and `site_design`. 1. Move the existing `full\blog_post.html.twig` file under `site_design`. 1. Copy it also under `de_design`. Modify the second one in any way (for example, add some html), so you can preview the effect. 1. To see the difference between the different themes, compare what is displayed at `/` and `/de/` ## Set up permissions To allow a group of users to edit only a specific content type (in this example, blog posts), you need to set up permissions for them. Users and user groups are assigned roles. A role can contain a number of policies, which are rules that permit the user to perform a specific function. Policies can be additionally restricted by limitations. 1. Go to **Admin** -> **Users**. Create a new user group (the same way you create regular content). Call the group "Bloggers". 1. In the new group create a user. Remember their username and password. Mark the user as "Enabled". *[Image: Creating a User]* 3. Go to **Admin** -> **Roles**. Create a new role called "Blogger". 1. Add policies that allow the user to log in: - `User/Login` - `Content/Read` - `Content/Versionread` - `Section/View` - `Content/Reverserelatedlist` 5. Now add policies that allow the user to create and publish content, limited to Blog Posts: - `Content/Create` with limitation for content type Blog Post - `Content/Edit` with limitation for content type Blog Post - `Content/Publish` with limitation for content type Blog Post *[Image: Adding limitations to a policy]* 6. In the **Assignments** tab assign the "Blogger" role to the "Bloggers" group. *[Image: Assigning a role]* You can now log out and log in again as the new user. You're able to create Blog Posts only. For more information, see [Permissions](https://doc.ibexa.co/en/latest/permissions/permissions/index.md). # Troubleshooting This page lists potential problems that you may encounter while installing, configuring, and running Ibexa DXP. ## Encoding database password The password entered in `DATABASE_URL` during installation must either be URL encoded, or not contain any special characters that would require URL encoding. ### URL encoding Using URL encoding involves two steps. First, the password must be URL encoded. This can for instance be done with PHP's `urlencode()` function. For example, this function converts a password like `(/!=#Ƥ*;%?[` to `%28%2F%21%3D%23%C3%86%C2%A4%2A%3B%25%3F%5B`. Second, you must remove `resolve:` from `doctrine.dbal.url` in `config/packages/doctrine.yaml`. That means changing `%env(resolve:DATABASE_URL)%` to `%env(DATABASE_URL)%`. ### Avoid special characters If your password only contains letters a-z, A-Z, and numbers 0-9, you don't need to do any encoding. You can either create your password that way, in which case it's a good idea to make it longer to maintain entropy, keeping the password hard to guess for an attacker. Or, you can for instance convert your password with `bin2hex()`, so that, for example, `(/!=#Ƥ*;%?[` becomes `282f213d23c386c2a42a3b253f5b`. The output from `bin2hex` is limited to 0-9 and a-f. This more than doubles the length of the password, keeping entropy similar. ## Enabling swap with limited RAM If you have problems installing Ibexa DXP on a system with limited RAM (for example 1GB or 2GB), enable swap. It allows your operating system to use the hard disk to supplement RAM when it runs out. With swap enabled you're able to successfully run `php -d memory_limit=-1 bin/console ibexa:install`. When a system runs out of RAM, you may see `Killed` when trying to clear the cache (for example, `php bin/console --env=prod cache:clear` from your project's root directory). ## Upload size limit To make use of the back office, the defined maximum upload size must be consistent with the maximum file size defined in the content type by using a File, Media, or Image field. It's done by setting `LimitRequestBody` for Apache or `client_max_body_size` for nginx. For instance, if one of those fields is configured to accept files up to 10MB, then `client_max_body_size` (in case of nginx) should be set above 10MB, with a safe margin, for example to 15MB. You also need to define settings for uploading files in `php.ini`: `upload_max_filesize` and `post_max_size`. ## Cloning failed using an ssh key When dealing with Composer packages from [updates.ibexa.co](https://updates.ibexa.co), you may get a "Cloning failed using an ssh key" error if you tell Composer to download dev packages or to download from source. [updates.ibexa.co](https://updates.ibexa.co) currently supports only distribution packages in alpha stability or higher. To avoid the error, check the stability of packages and avoid using `--prefer-source`. ## Redis/Valkey sessions issues ### Inconsistent cache/session data If cache or session data is inconsistent across web servers, see [Redis clustering](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/persistence_cache/#redis-clustering), and make sure you only read/write to one active master instance at a time. ### Removed or refused sessions If sessions are removed or new sessions are refused, it's recommended to use a separated instance for sessions, that either never runs out of memory or uses an eviction policy that suits your needs. For more information, see [Cluster setup](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/sessions/#cluster-setup). ## Conflict with roave/security-advisories When you use `composer update` or `composer require`, a package may conflict with `roave/security-advisories`: ``` Your requirements could not be resolved to an installable set of packages. Problem 1 - ezsystems/ezpublish-legacy v5.4.10 conflicts with roave/security-advisories[dev-master]. (...) ``` This means there is a known security bug in the specific version of the package, `ezsystems/ezpublish-legacy`. In most cases this means that a fix is available in a newer version. If you increase your requirement to that version, the conflict is resolved. In the rare case when there is no fixed version, you can revert your requirement to an older version which doesn't have the bug. If you have to use the version with the bug (not recommended) you can use `composer remove roave/security-advisories`. In such case, require it again when the bug is fixed and the package is updated: `composer require roave/security-advisories:dev-master` ## Ibexa Cloud HTTP access credentials with Varnish If you're using Ibexa Cloud with Varnish for HTTP cache and you have [HTTP access control by login/password](https://fixed.docs.upsun.com/administration/web/configure-environment.html#http-access-control) enabled, configure the following variables in your Upsun environment: - `HTTPCACHE_USERNAME` - `HTTPCACHE_PASSWORD` # Tutorials # Tutorials Get started with tutorials to learn how to create a site with Ibexa DXP. - [Beginner tutorial](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/tutorials/beginner_tutorial/beginner_tutorial/): Go through a beginner tutorial which presents the Ibexa DXP content model and show how to configure and use templates to create a basic site. - [Page and Form tutorial](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/tutorials/page_and_form_tutorial/page_and_form_tutorial/): Go through a Page and Form tutorial to learn how to create modular Sites and how to manage forms and their submissions. - [Creating a Point 2D field type](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/tutorials/generic_field_type/creating_a_point2d_field_type/): Go through a field type tutorial to learn how to create a custom field type based on the built-in Generic field type. # Beginner tutorial This tutorial is a step-by-step guide to building an Ibexa DXP website. You can use it with both Ibexa Headless and Ibexa Experience. ## Intended audience The tutorial is intended for users who have little or no previous experience with Ibexa DXP. To follow it, you should: - Have basic knowledge of HTML and CSS. - Have basic knowledge of the database you've selected. ## Learning outcomes After finishing this tutorial, you should: - know how to construct the content model of a website. - be able to use templates to display your content according to your needs. - know how to manage users and permissions. ## Scenario In the course of this tutorial you can build a website for storing and sharing bike rides. It enables the user to add information and photos of their routes and indicate what interesting points can be visited during the trip. ## Steps In this tutorial you go through the following steps: 1. [Get ready](https://doc.ibexa.co/en/latest/tutorials/beginner_tutorial/1_get_ready/index.md) 2. [Create the content model](https://doc.ibexa.co/en/latest/tutorials/beginner_tutorial/2_create_the_content_model/index.md) 3. [Customize the front page](https://doc.ibexa.co/en/latest/tutorials/beginner_tutorial/3_customize_the_front_page/index.md) 4. [Display a single content item](https://doc.ibexa.co/en/latest/tutorials/beginner_tutorial/4_display_single_content_item/index.md) 5. [Display a list of content items](https://doc.ibexa.co/en/latest/tutorials/beginner_tutorial/5_display_a_list_of_content_items/index.md) 6. [Improve configuration](https://doc.ibexa.co/en/latest/tutorials/beginner_tutorial/6_improve_configuration/index.md) 7. [Embed content](https://doc.ibexa.co/en/latest/tutorials/beginner_tutorial/7_embed_content/index.md) 8. [Enable account registration](https://doc.ibexa.co/en/latest/tutorials/beginner_tutorial/8_enable_account_registration/index.md) # Step 1 — Get ready To begin the tutorial, you need a clean installation of Ibexa DXP. Get it by following the [install Ibexa DXP](https://doc.ibexa.co/en/latest/getting_started/install_ibexa_dxp/index.md) guide. You need a web server, a relational database and PHP. The clean installation contains only a root content item which displays a welcome page. *[Image: Front page after clean installation]* You can replace the welcome page with your own in step 3. To remove it for now, go to `config/packages/` and delete the `ibexa_welcome_page.yaml` file. You can now start creating the content model. # Step 2 — Create the content model How your content is structured is an important part of an Ibexa DXP project. Think of it as the database design of your application. To get full information, read the [content model](https://doc.ibexa.co/en/latest/content_management/content_model/index.md) documentation page. Below is a short introduction that only covers points needed for this tutorial. ## Content model overview The Ibexa DXP content repository is centered around **content items**. A content item is a single piece of content, for example an article, a product review, a place, and more. Every content item is an instance of a content type. Content types define what **Fields** are included in each content item. For example, an article could include fields such as *title*, *image*, *abstract*, *article's body*, *publication date* and *list of authors*. Fields can belong to one of the installed **field types**, about 30 in the default distribution. Each field type is built to represent a specific type of data: a text line, a block of rich text, an image, a collection of relations to content items, and more. You can find a complete list in the [field types reference](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/field_type_reference/index.md) section. Every field type may have its own options, and comes with its own editing and viewing interfaces. ## Add a content type The site use two content types: **Ride** and **Landmark**. A Ride is a route of a bike trip. It can include one or more Landmarks - interesting places you can see along the way. More than one Ride can visit the same Landmark, so it's similar to an N-N relationship model in a database. In this step you add the first content type, Ride. Go to the admin interface (`/admin`) and log in with the default username: `admin` using the password specified during installation. In the upper-right corner, click the avatar icon to unfold the drop-down menu and disable the [Focus mode](https://doc.ibexa.co/projects/userguide/en/5.0/getting_started/discover_ui/#focus-mode). In the main menu, go to **Content** -> **Content types**. You can see a list of **Content type groups**. They're used to group content types in a logical way. Select **Content** and then click the **Create** button. *[Image: Add a content type button]* Fill the form with this basic info: - **Name**: Ride - **Identifier**: `ride` Then create all fields with the following information: | Field type | Name | Identifier | Required | Searchable | Translatable | | ------------ | -------------- | ---------------- | -------- | ---------- | ------------ | | Text line | Name | `name` | yes | yes | yes | | Image Asset | Photo | `photo` | no | no | no | | Rich text | Description | `description` | yes | yes | yes | | Map location | Starting point | `starting_point` | yes | yes | no | | Map location | Ending point | `ending_point` | yes | yes | no | | Integer | Length | `length` | yes | yes | no | Confirm the creation of the content type by clicking **Save and close**. ## Create Rides > **Note: Note** > > If you're using Ibexa Experience, the root content item in your installation is a Page called "Ibexa Digital Experience Platform". > > For this tutorial, swap it with its child, a Folder called "Ibexa Platform". > > To do this, in the main menu go to **Content** -> **Content structure** -> **Ibexa Digital Experience Platform**, select the **Locations** tab and in the **Swap Locations** section navigate to "Ibexa Platform". > > You can learn how to work with Pages in [another tutorial](https://doc.ibexa.co/en/latest/tutorials/page_and_form_tutorial/page_and_form_tutorial/index.md). Go back to the content by selecting **Content structure** in the main menu. Then browse the content tree and create a Folder named *All Rides* by clicking the **Create content** button on the top right of the screen. Publish the Folder. While in the folder, create a few of Rides using the **Create content** button, add photos and publish them. *[Image: Ready for Step 3]* Once you have two or more Rides in the Folder, you're ready to customize the homepage of the website. # Step 3 — Customize the front page In this step you can create the global layout of your site, and display content by using custom templates. First, go to the root of the site (``). You should now see the home page of the clean install, without any kind of layout. You can customize this step by instructing the platform to use a custom template to render this content item. ## Content rendering configuration To use a custom template when rendering the root content, create a `content_view` configuration block for `ibexa`. > **Note: Note** > > When pasting YAML code, pay attention to indentation and levels. The code blocks shown here include the full structure of the YAML file to help you learn where to place new blocks. Be careful not to duplicate existing keys, because YAML doesn't allow it. Edit `config/packages/ibexa.yaml`. Add the following block under `site` while paying attention to indentation - `content_view` should be one level below `site`: ``` ibexa: system: site: content_view: full: home_page: template: full/home_page.html.twig match: Id\Location: 2 ``` This tells Ibexa DXP to use the `template` when rendering content with Location ID `2`. `2` is the default location for the root content item. `Id\Location` is one of several [view matchers](https://doc.ibexa.co/en/latest/templating/templates/view_matcher_reference/index.md) that you can use to customize rendering depending on different criteria. > **Note: Clear the cache** > > Each time you change the YAML files, you should clear the cache. It's not mandatory in dev environment. > > To clear the cache: > > ``` > php bin/console cache:clear > ``` ## Create template and layout ### Create the first template Next, you need to create the template that you indicated in configuration. For the time being, fill the template with a basic "Hello world" message. Create a `home_page.html.twig` file in `templates/full/`: ```

Hello World!

``` Refresh the page to see an unstyled version of the message. > **Note: Note** > > If you still see the welcome page, go to `config/packages/` and make sure you deleted the `ibexa_welcome_page.yaml` file. ### Add the site's main layout Most sites have a general layout which includes things like header with a logo or footer. It's displayed on every page, and the content of the page is placed inside it. To add a template like this to your site, create a `main_layout.html.twig` file in `templates/` and paste the following code into it: ``` Ibexa DXP Beginner Tutorial {{ encore_entry_link_tags('tutorial') }}
{% block content %} {% endblock %}
{{ encore_entry_script_tags('tutorial-js') }} ``` In the highlighted lines (12 and 89) the template takes advantage of [Symfony Webpack Encore](https://symfony.com/doc/7.4/frontend.html#webpack-encore). This tutorial leads you through configuring Webpack, but first you need assets. ### Adding assets The site has no stylesheets or assets yet. You need to download [`assets.zip`](https://doc.ibexa.co/en/latest/tutorials/beginner_tutorial/img/assets.zip) which contains the prepared asset files. Then unpack its contents to the following directories: - `css`, `fonts`, and `js` folders to `assets/` - `images` folder to `public/assets/` Before proceeding, ensure that the structure of the added files looks like this: *[Image: File structure]* ### Configuring Webpack In Ibexa DXP, you can add assets by using [Symfony Webpack Encore](https://symfony.com/doc/7.4/frontend.html#webpack-encore) — an integration of Webpack that enables you to build bundles of CSS stylesheets and JS scripts and add them to the project. For more information, see [Importing assets from a bundle](https://doc.ibexa.co/en/latest/administration/back_office/back_office_elements/importing_assets_from_bundle/index.md). To create bundles, first, indicate which files to include in them. Open the `webpack.config.js` file located in the root folder of your project. Paste the following code right under `Encore.addEntry('app', './assets/app.js');`: ``` Encore .addStyleEntry('tutorial', [ path.resolve(__dirname, './assets/css/normalize.css'), path.resolve(__dirname, './assets/css/bootstrap.min.css'), path.resolve(__dirname, './assets/css/bootstrap-theme.css'), path.resolve(__dirname, './assets/css/style.css') ]) .addEntry('tutorial-js', [ path.resolve(__dirname, './assets/js/bootstrap.min.js') ]); ``` `.addStyleEntry('tutorial', [])` and `.addEntry('tutorial-js', [])` refer to `{{ encore_entry_link_tags('tutorial') }}` and `{{ encore_entry_script_tags('tutorial-js') }}` from `main_layout.html.twig`. This configuration creates a bundle consisting of files to be added to a template. At this point the bundles are created and ready to be used. ### Extending templates Now you have to add the `main_layout.html.twig` template that uses the assets to the `home_page.html.twig` template. To add one template to another, edit `templates/full/home_page.html.twig` and replace it with the following code: ``` {% extends "main_layout.html.twig" %} {% block content %}

Hello World!

{% endblock %} ``` The templating language Twig supports [template inheritance](https://twig.symfony.com/doc/3.x/tags/extends.html). Templates can contain named blocks. Any template can extend other templates, and modify the blocks defined by its parents. The code above points to `main_layout.html.twig` in line 1. It also wraps your "Hello world" message in a `content` block. If you look back at the main layout template, you can see an empty `{% block content %}{% endblock %}` section (lines 52-53). This is where the `home_page.html.twig` is rendered. Clear the cache and regenerate the assets by running the following commands: ``` php bin/console cache:clear php bin/console assets:install yarn encore ``` > **Tip: Tip** > > You should run the `yarn encore` command with the [environment](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/environments/index.md) you're using. > > By default, Ibexa DXP installs in the dev environment. If you changed it to prod, use `yarn encore prod`. Refresh the page and you should see the "Hello world" placed inside a styled layout. *[Image: Homepage with a Hello world]* At this point, the template is static. It doesn't render any dynamic data from the repository. # Step 4 — Display a single content item You render a list of all Rides here in the next step. But before that, you can use the existing page layout to render the content of a single Ride. ### Create the Ride view Create a Twig template `templates/full/ride.html.twig` with the following code: ``` {% extends "main_layout.html.twig" %} {% block content %}

{{ content.name }}

{{ 'Starting point'|trans }}

{{ ibexa_render_field(content, 'starting_point', {'parameters': { 'width': '100%', height: '200px', 'showMap': true, 'showInfo': false }}) }}

{{ 'Ending point'|trans }}

{{ ibexa_render_field(content, 'ending_point', {'parameters': { 'width': '100%', height: '200px', 'showMap': true, 'showInfo': false }}) }}

{{ ibexa_render_field( content, 'length') }} km

{{ 'Description'|trans }}

{{ ibexa_render_field( content, 'description') }}
{% endblock %} ``` This template reuses `main_layout.html.twig` and again places the template in a `content` block. > **Tip: Previewing available variables** > > You can see what variables are available in the current template with the `dump()` Twig function: > > ``` > {{ dump() }} > ``` > > You can also dump a specific variable: > > ``` > {{ dump(location) }} > ``` Now you need to indicate when this template should be used. Go back to `config/packages/ibexa.yaml` and add the following configuration (under the existing `content_view` and `full` keys:): ``` site: content_view: full: # existing keys, don't change them ride: template: full/ride.html.twig match: Identifier\ContentType: ride ``` This tells the application to use this template whenever it renders the full view of a Ride. ### Check the Ride full view Because you don't have a list of Rides on the front page yet, you cannot click a Ride to preview it. But you still can see how the template works in two ways: #### Preview in the back office You can use the [preview](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/preview_content_items/) while editing in the back office to see how the content is rendered in full view. *[Image: Full ride preview in admin]* #### Go to the Ride page You can also go directly to the URL of a Ride. The URL for a Ride content item located in the "All Rides" Folder is `http:///all-rides/`. # Step 5 — Display a list of content items Now that you know how to display a single content item, you can take care of rendering a list of content items. In this step you can display a table of all Rides on the front page. ## Customize the homepage template In `templates/full/home_page.html.twig` replace the "Hello world" with a table that displays the list of all existing Rides: ``` {% extends "main_layout.html.twig" %} {% block content %}
{% for ride in rides.currentPageResults %} {{ render( controller( 'ibexa_content::viewAction', { 'location': ride.valueObject, 'viewType': 'line' } )) }} {% endfor %}
{{ 'Ride'|trans }} {{ 'From'|trans }} {{ 'To'|trans }} {{ 'Distance'|trans }}
{% if rides.haveToPaginate() %}
{% endif %}
{% endblock %} ``` The `rides` variable you use in line 15 above needs to contain a list of all Rides. To get this list, you use a Query Type. ## Create a QueryType for the home page QueryType objects are used to limit and sort results for content item queries. For more information, see [Built-In Query Types](https://doc.ibexa.co/en/latest/templating/queries_and_controllers/built-in_query_types/index.md). Here, you need to display `ride` objects that have been published (are visible). Create a `RideQueryType.php` file in `src/QueryType`: ``` new Criterion\LogicalAnd( [ new Criterion\Visibility(Criterion\Visibility::VISIBLE), new Criterion\ContentTypeIdentifier(['ride']), ] ) ]); } public function getSupportedParameters() { } } ``` This Query Type finds all visible content items that belong to the `ride` content type (lines 21-22). Now you need to indicate that this Query Type is used in your configuration. ## Add Query Type to configuration Edit `config/packages/ibexa.yaml`. In the view configuration for the home page indicate that this view uses the Query Type: ``` site: content_view: full: # existing keys, don't change them home_page: controller: ibexa_query::pagingQueryAction template: full/home_page.html.twig match: Id\Location: 2 params: query: query_type: Ride limit: 4 assign_results_to: rides ``` The `query_type` parameter in line 12 indicates which Query Type to use. You defined the name `Ride` in the Query Type file in the `getName` method. Using the `pagingQueryAction` of the built-in `ibexa_query` controller (line 6) enables you to automatically get paginated results. You can set the limit of results per page in the `limit` parameter. ### View types So far you have been using the `full` view type to render the Ride's full view. Here, on the other hand, you use the `line` view, as indicated by `'viewType': 'line'` in the home page template (line 16). You can configure custom view types with any name you want, as long as you include them in the configuration. Let's do this now with the `line` view for Rides. ## Create a line template for Rides Add a rule for the `ride` template in your `config/packages/ibexa.yaml` file. `line` should be at the same level as `full`. ``` system: site: content_view: line: ride: template: line/rides.html.twig match: Identifier\ContentType: ride ``` Create the `templates/line/rides.html.twig` template. Because this template is rendered inside a table, it starts with a `` tag. ``` {{ content.name }} {% if not ibexa_field_is_empty( content, 'photo' ) %} {{ ibexa_render_field(content, 'photo') }} {% endif %} {{ ibexa_render_field(content, 'starting_point', {'parameters': {'width': '100%', 'height': '100px', 'showMap': true, 'showInfo': true }}) }} {{ ibexa_render_field(content, 'ending_point', {'parameters': {'width': '100%', 'height': '100px', 'showMap': true, 'showInfo': true }}) }}

{{ ibexa_render_field( content, 'length' ) }} Km

``` ### Add Media permission To be able to view the `photo` field you have to add a `read` permission to `Media` section. In the main menu, go to **Admin** (gear icon) -> **Roles**, and click the **Anonymous** role. *[Image: Policies for the Anonymous Role without Media section]* Edit the **Content/Read** policy line to add the `Media` section to **Limitation** along with the `Standard` section. *[Image: Policies for the Anonymous Role with Media section]* Now go to the homepage of your website and you can see the list of Rides. However, the Ride photos are too large and stretch the table. In the next step you can ensure they're displayed in proper size. # Step 6 — Improve configuration ## Define image variations Image variations are different versions of the same image. You can use them, for example, to scale images, crop them, or add effects. So far the images in the ride list are fitted to the templates automatically, and the result doesn't look good. Now you can create a variation to specify how you want the images to look in detail. Create a new `config/packages/image_variations.yaml` file containing: ``` ibexa: system: default: image_variations: ride_list: reference: null filters: - {name: geometry/scaledownonly, params: [140, 100]} ``` Next, modify the templates to use these variations. Variation names are provided as parameters when rendering the image content. In `templates/line/rides.html.twig` add the `'alias': 'ride_list'` parameter in the following way, in lines 8-10: ``` {% if not ibexa_field_is_empty( content, 'photo' ) %} {{ ibexa_render_field(content, 'photo', { 'parameters': { 'alias': 'ride_list' } }) }} {% endif %} ``` This ensures that the photo displayed next to each Ride in the list is scaled down properly with proportions retained. Clear cache and refresh the front page. Photos should now have a regular size and fit in the table. *[Image: Ride list with proper image variations]* ## Separate view configuration In a larger site there are many elements that need configuration. To keep it more organized, you can split parts of configuration into separate files. As an example, you can separate all content view configuration into its own file. Create a `config/packages/views.yaml` file. Copy everything under `content_view` from `config/packages/ibexa.yaml` and move it to the new file. Remove the corresponding code from `ibexa.yaml`. The `views.yaml` file should look like this: ``` ibexa: system: site: content_view: full: home_page: controller: ibexa_query::pagingQueryAction template: full/home_page.html.twig match: Id\Location: 2 params: query: query_type: Ride limit: 4 assign_results_to: rides ride: template: full/ride.html.twig match: Identifier\ContentType: ride line: ride: template: line/rides.html.twig match: Identifier\ContentType: ride ``` # Step 7 — Embed content Creating lists and detailed views of content types and their respective items often involves loading related resources. In this step, you add a related object, a Landmark, which is displayed on Ride pages. You can add as many or as little related resources as you like. ## Add the Landmark content type Now you need to add the second content type needed in the site, Landmark. Go to **Content types**, and in the **Content** group, add the Landmark content type. A Landmark is an interesting place that Rides go through. Each Ride may be related to multiple Landmarks. - **Name**: Landmark - **Identifier**: landmark Then add all fields with the following information: | Field type | Name | Identifier | Required | Searchable | Translatable | | ------------ | ----------- | ------------- | -------- | ---------- | ------------ | | Text line | Name | `name` | yes | yes | yes | | Rich text | Description | `description` | no | yes | yes | | Image Asset | Photo | `photo` | yes | no | no | | Map location | Location | `location` | yes | yes | no | Confirm the creation of the content type by selecting **Create**. Create a *Landmarks* Folder and add some Landmarks to it. You need pictures (for the Photo field) to represent them. ## Add Landmarks to Ride content type definition Now edit the Ride content type to add a Multiple Content Relation between the two content types. Create a new **Content relations (multiple)** field called "Landmarks" with identifier `landmarks` and allow content type "Landmark" to be added to it: *[Image: Adding Landmarks to the Ride content type]* Confirm by clicking **Save and close**. Go back to one of your existing Rides, edit it and link some Landmarks to it. Click **Publish**. ## Display a list of Landmarks in Ride view ### Create Landmark line view Now you need to create the line view for Landmarks. Declare a new override rule in `config/packages/views.yaml`: ``` ibexa: system: site: content_view: #full views here line: landmark: template: line/landmark.html.twig match: Identifier\ContentType: landmark ``` Add the template for the line view of a Landmark by creating `templates/line/landmark.html.twig`: ```
{# MODAL #}
``` Like before, you use an image variation here (line 4) and you need to configure it. Add the following section to `config/packages/image_variations.yaml`, at the same level as `ride_list`: ``` landmark_list: reference: null filters: - {name: geometry/scalewidth, params: [200]} ``` ### Create the RideController You must provide additional information when the Ride object is displayed. This requires creating a custom controller. The controller uses `ContentService` to load related resources (Landmarks) for a particular Ride. Create a `src/Controller/RideController.php` file: ``` contentService = $contentService; } public function viewRideWithLandmarksAction(ContentView $view) { $currentContent = $view->getContent(); $landmarksListId = $currentContent->getFieldValue('landmarks'); $landmarksList = []; foreach ($landmarksListId->destinationContentIds as $landmarkId) { $landmarksList[$landmarkId] = $this->contentService->loadContent($landmarkId); } $view->addParameters(['landmarksList' => $landmarksList]); return $view; } } ``` Update `config/packages/views.yaml` to mention the `RideController.php` by adding a line with the `controller` key to the view config: ``` ibexa: system: site: content_view: full: ride: template: full/ride.html.twig controller: App\Controller\RideController::viewRideWithLandmarksAction match: Identifier\ContentType: ride ``` ### Add the Landmark in the Ride full view Now modify the Ride full view template to include a list of Landmarks, and the controller that you created. Add the following lines at the end of `templates/full/ride.html.twig`, before the last `` and the closing tag `{% endblock %}`: ``` {% if landmarksList is not empty %}

{{ 'Landmarks'|trans }}

{% for landmark in landmarksList %} {{ render( controller( "ibexa_content::viewAction", { 'content': landmark, 'viewType': 'line'} )) }} {% endfor %}
{% endif %} ``` You can now check the Ride page again to see all the connected Landmarks. > **Tip: Tip** > > You can use `dump()` in Twig templates to display all available variables. *[Image: Ride full view with Landmarks]* # Step 8 — Enable account registration In this step you enable other users to create accounts on your site, access the back office and create content. ## Enable registration In the main menu, go to **Admin** (gear icon) -> **Roles**, and click the **Anonymous** role. *[Image: Available roles]* Add the `User/Register` policy to the Anonymous user. This allows any visitor to the website to access the registration form. *[Image: Policies for the Anonymous Role]* Then go to `/register`. The registration form is unstyled, so you need to add templates to it. ## Customize registration forms In the `config/packages/views.yaml` file add a `user_registration` key under `site`, at the same level as `content_view`: ``` ibexa: system: site: # existing content_view keys user_registration: templates: form: user/registration_form.html.twig ``` This indicates which template is used to render the registration form. Create the file `templates/user/registration_form.html.twig`: ``` {% extends "main_layout.html.twig" %} {% block page_head %} {% set title = 'Register user'|trans %} {{ parent() }} {% endblock %} {% block content %} {% import 'user/registration_content_form.html.twig' as registrationForm %}

{{ 'Member Registration'|trans }}

* {{ 'All fields are required'|trans }}
{{ registrationForm.display_form(form) }}
{% endblock %} ``` In line 10 you can see that another file is imported: `registration_content_form.html.twig`. The second template renders the actual fields of the registration form. Create this file as well (as `templates/user/registration_content_form.html.twig`): ``` {% macro display_form(form) %} {{ form_start(form) }} {% for fieldForm in form.fieldsData %} {% set fieldIdentifier = fieldForm.vars.data.fieldDefinition.identifier %} {% if fieldIdentifier == 'first_name' or fieldIdentifier == 'last_name' %} {% if fieldIdentifier == 'first_name' %}
{% endif %}
{{ form_errors(fieldForm.value) }} {{ form_widget(fieldForm.value, { 'contentData': form.vars.data }) }}
{% if fieldIdentifier == 'last_name' %}
{% endif %} {% endif %} {% if fieldIdentifier == 'user_account' %}
{{ form_widget(fieldForm.value, { 'contentData': form.vars.data }) }}
{% endif %} {%- do fieldForm.setRendered() -%} {% endfor %}
{{ form_widget(form.register, {'attr': { 'class': 'btn btn-block btn-primary' }}) }}
{{ form_end(form) }} {% endmacro %} ``` The third template you need to prepare covers the confirmation page that is displayed when a user completes the registration. First, point to the new template in the configuration. Add a `confirmation` key to `config/packages/views.yaml`: ``` user_registration: templates: form: user/registration_form.html.twig confirmation: user/registration_confirmation.html.twig ``` Then create the `templates/user/registration_confirmation.html.twig` template: ``` {% extends "main_layout.html.twig" %} {% block page_head %} {% set title = 'Registration complete'|trans %} {{ parent() }} {% endblock %} {% block content %}

{{ 'Registration completed'|trans }}

{{ 'You\'re all set up and ready to go'|trans }}
{% endblock %} ``` Now return to `/register`: *[Image: Complete Register page with the layout]* Fill in the form and register a user. > **Tip: Tip** > > If you log in as the new user at this point, you need to go to the back office (`/admin`) to log out again re-log in as Admin. ## Set up Permissions Users created through the registration form are placed in the *Guest accounts* user group. The user you created has the roles assigned to this group. > **Tip: Tip** > > You can change the group in which new users are placed (but you don't need to do it for this tutorial). For more information, see [Registering new users](https://doc.ibexa.co/en/latest/users/user_registration/index.md). At this point you don't want anyone who registers to be able to add content to the website. That's why you need to create a new user group with additional permissions. When the administrator accepts a new user, they can move them to this new group. ### Create a user group In the back office, go to **Admin** -> **Users**, click the **Create content** button and create a user group named `Go Bike Members`. ### Create a Folder for contributed Rides Go to the `All Rides` Folder and create inside it a new Folder named `Member Rides`. Go Bike Members are only able to create Content in this Folder. ### Set permissions for Go Bike Members From Admin in the **Roles** screen, create a new role named *Contributors*. Now add the following policies to the Contributors role. - User/Login - User/Password - Content/Read - Content/Versionread - Content/Create with limitations: content type limited to Ride and Landmark content types and subtree to the `Member Rides` - Content/Publish with limitations: content type limited to Ride and Landmark content types and subtree to the `Member Rides` - Content/Edit with limitation: Owner limited to `Self` - Section/View - Content/Reverserelatedlist > **Tip: Tip** > > The limitations are a powerful tool for fine-tuning the permission management of the users. See [the documentation about limitations for more technical details](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#content-type-group-limitation). Once the policies are set, go to the **Assignments** tab and assign the role to the user group *Go Bike Members*. Next, go to the users page. Select the user you created and move them into the *Go Bike Members* user group. ### Create content as a Go Bike Member Log out as admin and then log in again into the back office with the credentials of the new user. You now have the ability to create new Rides and Landmarks in the selected folder. ## Congratulations! Now you have created your first website with Ibexa DXP. **You learned how to:** - create a content model - organize files in an Ibexa DXP project - configure views for different content types - add assets to an Ibexa DXP project - use and configure Webpack Encore - use Twig templates and controllers to display content - enable user registration - manage user permissions # Page and Form tutorial Editions: Experience This tutorial is a step-by-step guide to building an advanced website with Ibexa Experience. It focuses on creating a front page using a feature called **Page Builder**. ### Intended audience This tutorial is intended for users who have basic knowledge of Ibexa DXP. Ideally, you should be familiar with the concepts covered in the [Beginner tutorial](https://doc.ibexa.co/en/latest/tutorials/beginner_tutorial/beginner_tutorial/index.md). ### Learning outcomes After finishing this tutorial, you: - have a working knowledge of the Page functionality and architecture - are able to create a Page and customize its layout - are able to prepare and customize Page blocks - are able to create a custom block - know how to use Form Builder and configure your form - know how to apply custom styling to blocks ## Scenario In the course of this scenario you can build a website for a magazine for dog owners called 'It's a Dog's World'. You can create a welcome page that showcases the magazine's three most important types of content: articles, dog breed information and tips. You do this by means of a Page, making use of its specific blocks, and crafting your own as well. *[Image: It's a Dog's World - final result]* ## Steps In this tutorial you go through the following steps: 1. [Get a starter website](https://doc.ibexa.co/en/latest/tutorials/page_and_form_tutorial/1_get_a_starter_website/index.md) 2. [Prepare the Page](https://doc.ibexa.co/en/latest/tutorials/page_and_form_tutorial/2_prepare_the_landing_page/index.md) 3. [Use existing blocks](https://doc.ibexa.co/en/latest/tutorials/page_and_form_tutorial/3_use_existing_blocks/index.md) 4. [Create a custom block](https://doc.ibexa.co/en/latest/tutorials/page_and_form_tutorial/4_create_a_custom_block/index.md) 5. [Create a newsletter form](https://doc.ibexa.co/en/latest/tutorials/page_and_form_tutorial/5_create_newsletter_form/index.md) # Step 1 — Get a starter website Editions: Experience To set up the starter website, you need to follow these steps: ## Get a clean Ibexa DXP installation To begin the tutorial, you need a clean installation of Ibexa Experience. Get it by following the [Install Ibexa DXP](https://doc.ibexa.co/en/latest/getting_started/install_ibexa_dxp/index.md) guide. ## Add content types Log in to the back office – add `/admin` to your installation's address (`/admin`) and log in as `admin` user using the password specified during installation. Disable the Focus mode, go to content types screen and (under the content category) add two content types with the following settings: ### Dog Breed - **Name:** Dog Breed - **Identifier:** `dog_breed` - **Fields:** | Field type | Name | Identifier | Required | Searchable | Translatable | | ----------- | ----------------- | ------------------- | -------- | ---------- | ------------ | | Text line | Name | `name` | yes | yes | yes | | Text line | Short Description | `short_description` | yes | yes | yes | | Image Asset | Photo | `photo` | yes | no | no | | RichText | Full Description | `description` | yes | yes | yes | ### Tip - **Name:** Tip - **Identifier:** `tip` - **Fields:** | Field type | Name | Identifier | Required | Searchable | Translatable | | ---------- | ----- | ---------- | -------- | ---------- | ------------ | | Text line | Title | `title` | yes | yes | yes | | Text block | Body | `body` | no | no | yes | ### Modify existing Article content type You also need to modify the built-in Article content type. It makes inserting photos into articles easier. Edit it to remove the Image field that has a Content Relation (ibexa_object_relation) type, and create a new field in its place: | Field type | Name | Identifier | Required | Searchable | Translatable | | ----------- | ----- | ---------- | -------- | ---------- | ------------ | | Image Asset | Image | `image` | yes | no | no | *[Image: New image field in the Article content type]* ## Add template, configuration and style files > **Tip: Tip** > > For an introduction on how to use templates in Ibexa DXP, see [Beginner tutorial](https://doc.ibexa.co/en/latest/tutorials/beginner_tutorial/beginner_tutorial/index.md). First, to remove the welcome page, go to `config/packages/` and delete the `ibexa_welcome_page.yaml` file. Place the [`pagelayout.html.twig`](https://github.com/ibexa/documentation-developer/blob/5.0/code_samples/tutorials/page_tutorial_starting_point/templates/pagelayout.html.twig) and [`pagelayout_menu.html.twig`](https://github.com/ibexa/documentation-developer/blob/5.0/code_samples/tutorials/page_tutorial_starting_point/templates/pagelayout_menu.html.twig) files in the `templates` folder. Create a new folder, called `full`, in `templates`. Place further template files in it: - [`article.html.twig`](https://github.com/ibexa/documentation-developer/blob/5.0/code_samples/tutorials/page_tutorial_starting_point/templates/full/article.html.twig) - [`dog_breed.html.twig`](https://github.com/ibexa/documentation-developer/blob/5.0/code_samples/tutorials/page_tutorial_starting_point/templates/full/dog_breed.html.twig) - [`folder.html.twig`](https://github.com/ibexa/documentation-developer/blob/5.0/code_samples/tutorials/page_tutorial_starting_point/templates/full/folder.html.twig) - [`tip.html.twig`](https://github.com/ibexa/documentation-developer/blob/5.0/code_samples/tutorials/page_tutorial_starting_point/templates/full/tip.html.twig) Place two configuration files in the `config/packages` folder: - [`views.yaml`](https://github.com/ibexa/documentation-developer/blob/5.0/code_samples/tutorials/page_tutorial_starting_point/config/packages/views.yaml) - [`image_variations.yaml`](https://github.com/ibexa/documentation-developer/blob/5.0/code_samples/tutorials/page_tutorial_starting_point/config/packages/image_variations.yaml) In the `assets` folder in the project root: - create a `css` folder and add the following stylesheet: [`style.css`](https://github.com/ibexa/documentation-developer/blob/5.0/code_samples/tutorials/page_tutorial_starting_point/assets/css/style.css) to it - add the [`header.jpg`](https://github.com/ibexa/documentation-developer/blob/5.0/code_samples/tutorials/page_tutorial_starting_point/assets/images/header.jpg) file to the `assets/images` folder In the `webpack.config.js` file in the project root folder, add the following line after `Encore.addEntry('app', './assets/app.js');`: ``` Encore.addStyleEntry('tutorial', [path.resolve(__dirname, './assets/css/style.css')]); ``` Next, in the terminal run the commands: ``` yarn encore php bin/console cache:clear ``` > **Tip: Tip** > > Compiling assets with Webpack Encore is explained in [the beginner tutorial](https://doc.ibexa.co/en/latest/tutorials/beginner_tutorial/3_customize_the_front_page/#configuring-webpack). In the `src` folder create a `QueryType` subfolder and add [`QueryType/MenuQueryType.php`](https://github.com/ibexa/documentation-developer/blob/5.0/code_samples/tutorials/page_tutorial_starting_point/src/QueryType/MenuQueryType.php) to it. This file takes care of displaying the top menu (for more information, see [the documentation](https://doc.ibexa.co/en/latest/templating/queries_and_controllers/content_queries/#query-types)). The structure of the new and modified files should look like: *[Image: File structure]* ## Create content Now return to the back office and create some content for your website. First, you can hide unneeded content items from the project root. Go to **Content structure** and select "Ibexa Digital Experience Platform". In the **Sub-items** tab, select all the current sub-items and click the **Hide** icon: *[Image: Hiding content items you don't need]* Next, under "Ibexa Digital Experience Platform", create three Folders. Call them 'All Articles', 'Dog Breed Catalog' and 'All Tips'. Remember that you can **Save and close** them, but you should use the **Publish** button. Next, create a few content items of proper content types in each of these folders: - 4 Articles (at least, to best see the effects of the Content Scheduler block that you can create in step 3.) - 3 Dog Breeds - 3 Tips ### Add images When you need an image, you can use one from [this image pack](https://doc.ibexa.co/en/latest/tutorials/page_and_form_tutorial/img/photos.zip). This lets you compare effects of your work to screenshots in the tutorial. At this point you're ready to proceed with the next step. # Step 2 — Prepare the Page Editions: Experience In this step you can prepare and configure your front page, together with its layout and templates. ## Create Page layout Go to the front page of your website (``). You can see that it looks unfinished. However, you can still use the menu and look around the existing content in the website. *[Image: It's a Dog's World - Starting point]* > **Tip: Tip** > > At any point in the tutorial if you don't see the results of your last actions, try clearing the cache and regenerating assets: > > `php bin/console cache:clear` > > `yarn encore ` Log in to the back office. Go to **Content Structure**. The **Ibexa Digital Experience Platform** content item is the first page that is shown to the visitor. Here you can check what content type it belongs to: it's a landing page. *[Image: Ibexa Digital Experience Platform is a landing page]* The page contains one Code block and is displayed without any template. Click **Edit** to enter a mode that enables you to work with pages. You can see that the home page has only one zone with the block. *[Image: Empty Page with default layout]* Remove the Tag block. Hover over it and select the trash icon from the menu. Click the **Field** button on the left of the top bar to switch to editing page fields. Change the Title of the page to "Home". Then, publish the page to update its name. The design for the website you're making needs a layout with two zones: a main column and a narrower sidebar. Ibexa Experience provides only a one-zone default layout, so you need to create a new one. Preparing a new layout requires three things: - entry in configuration - thumbnail - template #### Add entry in configuration First create a new file for layout configuration, `config/packages/ibexa_fieldtype_page.yaml`: ``` ibexa_fieldtype_page: layouts: sidebar: identifier: sidebar name: Right sidebar description: Main section with sidebar on the right thumbnail: /assets/images/layouts/sidebar.png template: layouts/sidebar.html.twig zones: first: name: First zone second: name: Second zone ``` #### Add thumbnail > **Tip: Tip** > > For a detailed description of creating a Page layout, see [Page layouts](https://doc.ibexa.co/en/latest/templating/render_content/render_page/#render-a-layout). The `sidebar` (line 3) is the internal key of the layout. `name` (line 5) is displayed in the interface when the user selects a layout. The `thumbnail` (line 7) points to an image file that is shown when creating a new landing page next to the name. Use the [supplied thumbnail file](https://github.com/ibexa/documentation-developer/blob/5.0/code_samples/tutorials/page_tutorial_starting_point/public/assets/images/layouts/sidebar.png) and place it in the `public/assets/images/layouts/` folder. The `template` (line 8) points to the Twig file containing the template for this layout. #### Create page template Configuration points to `sidebar.html.twig` as the template for the layout. The template defines what zones are available in the layout. Create a `templates/layouts/sidebar.html.twig` file: ```
{% if zones[0].blocks %} {% set locationId = parameters.location is not null ? parameters.location.id : contentInfo.mainLocationId %} {% for block in zones[0].blocks %}
{{ render_esi(controller('Ibexa\\Bundle\\FieldTypePage\\Controller\\BlockController::renderAction', { 'locationId': locationId, 'contentId': contentInfo.id, 'blockId': block.id, 'versionNo': versionInfo.versionNo, 'languageCode': field.languageCode })) }}
{% endfor %} {% endif %}
``` The above template creates two columns and defines their widths. Each column is at the same time a zone, and each zone renders the blocks that it contains. > **Tip: Tip** > > In sites with multiple layouts you can separate the rendering of zones into a separate `zone.html.twig` template to avoid repeating the same code in every layout. > **Note: Note** > > A zone in a layout template **must have** the `data-ibexa-zone-id` attribute (lines 2 and 19). A block **must have** the `data-ibexa-block-id` attribute (lines 7 and 24). With these three elements: configuration, icon and template, the new layout is ready to use. ### Change Home Page layout Now you can change the Home Page to use the new layout. Edit Home and in the top bar select **Switch layout**. Choose the new layout called "Main section with sidebar on the right". The empty zones you defined in the template are visible in the editor. *[Image: Select layout window]* > **Tip: Tip** > > If the new layout isn't available when editing the page, you may need to clear the cache (using `php bin/console cache:clear`) and/or reload the app. *[Image: Empty page with new layout]* Publish the Home page. You can notice that it still has some additional text information. This is because the looks of a page are controlled by two separate template files, and you have only prepared one of those. The `sidebar.html.twig` file defines how zones are organized and how content is displayed in them. But you also need a general template file that is used for every page, regardless of its layout. Add this new template, `templates/full/landing_page.html.twig`: ``` {% extends 'pagelayout.html.twig' %} {% block content %}
{{ ibexa_render_field(content, 'page') }}
{% endblock %} ``` This template renders the page content. If there is any additional content or formatting you would like to apply to every page, it should be placed in this template. Now you need to tell the app to use this template to render pages. Edit the `config/packages/views.yaml` file and add the following code under the `full:` key: ``` landing_page: template: full/landing_page.html.twig match: Identifier\ContentType: landing_page ``` After adding this template you can check the new page. The part between menu and footer should be empty, because you haven't added any content to it yet. *[Image: Empty Page]* # Step 3 — Use existing blocks Editions: Experience In this step you can add a Content List block and a Content Scheduler block and customize them. ### Add a Content List block First, create an override template for the Content List block: `templates/blocks/contentlist/default.html.twig`: ```

{{ parentName }}

{% if contentArray|length > 0 %}
{% for content in contentArray %}
{{ ibexa_render_field(content.content, 'photo', { 'parameters': { 'alias': 'content_list' } }) }}

{{ ibexa_content_name(content.content) }}

{% if not ibexa_field_is_empty(content.content, 'short_description') %}
{{ ibexa_render_field(content.content, 'short_description') }}
{% endif %}
{% endfor %}
{% endif %}
``` Then add a configuration that tells the app to use this template instead of the default one. In `config/packages/ibexa_fieldtype_page.yaml` add the following code at the end of the file, under the `ibexa_fieldtype_page` key on the same level as `layouts`: ``` blocks: contentlist: views: default: template: blocks/contentlist/default.html.twig name: Content List ``` The template makes use of an [image variation](https://doc.ibexa.co/en/latest/content_management/images/images/index.md) (line 10). It's the thumbnail of the Dog Breed image that is displayed in the block. To configure this variation, open the `config/packages/image_variations.yaml` file and add the following code under the `image_variations` key: ``` content_list: reference: null filters: - { name: geometry/scaleheightdownonly, params: [ 81 ] } - { name: geometry/crop, params: [ 80, 80, 0, 0 ] } ``` Finally, add some styling to the block. Add the following CSS to the end of the `assets/css/style.css` file: ``` /* Landing Page */ @media only screen and (min-width: 992px) { aside > div { padding-left: 45px; } } /* Content list block */ .content-list-item { clear: left; min-height: 90px; padding-bottom: 5px; border-bottom: 1px solid black; } .content-list h5 { font-size: 1.3em; } .content-list-item-image { float: left; margin-right: 10px; } ``` Run `yarn encore ` to regenerate assets. At this point you can start adding blocks to the page. You do it in the page's Edit mode by dragging a block from the menu on the right to the correct zone on the page. Drag a Content List block from the menu to the left zone on the page. Click the block and fill in the form. Here you name the block and decide what it displays. Choose the "Dog Breed Catalog" folder as the Parent, select Dog Breed as the content type to be displayed, and choose a limit (3). Display the first three Dog Breeds from the database. *[Image: Window with Content List options]* Click **Submit** and you should see a preview of what the block looks like with the dog breed information displayed. *[Image: Content List Styled]* The block is displayed using the new template. Built-in blocks have default templates included in a clean installation, but you can override them. Publish the page now and move on to creating another type of block. ### Create a Content Scheduler block for featured articles The next block is the Content Scheduler block that airs articles at predetermined times. First, add a configuration that points to the layout. Go to `config/packages/ibexa_fieldtype_page.yaml` again and add the following code under `blocks` on the same level as the `contentlist` key: ``` schedule: views: featured: template: blocks/schedule/featured.html.twig name: Featured Schedule Block ``` The configuration defines one view for the Schedule block called `featured` and points to a `featured.html.twig` template. Create the new file `templates/blocks/schedule/featured.html.twig`: ``` {% apply spaceless %}
{% endapply %} ``` When you look at the template, you can see three blocks, each of which render the content items using the `featured` view (line 11). So far you only have templates for `full` view for Articles. This means you need to create a `featured` view template, otherwise you get an error when trying to add content to the block. You need to modify the `config/packages/views.yaml` file to indicate when to use the template. Add the following code to this file, on the same level as the `full` key: ``` featured: article: template: featured/article.html.twig match: Identifier\ContentType: article ``` Now create a `templates/featured/article.html.twig` file: ``` {% set imageAlias = ibexa_image_alias(content.getField('image'), content.versionInfo, 'featured_article') %}

{{ ibexa_content_name(content) }}

``` Like in the case of the Content List block, the template specifies an image variation. Add it in `config/packages/image_variations.yaml` under the `image_variations` key: ``` featured_article: reference: null filters: - { name: geometry/scaleheightdownonly, params: [ 200 ] } ``` The Block is already operational, but first update the stylesheet. Add the following CSS at the end of the `assets/css/style.css` file: ``` /* Featured articles Content Scheduler block */ .featured-article-container { background-size: cover; padding: 0; margin-bottom: 20px; } .featured-article { height: 200px; padding: 0; background-repeat: no-repeat; } .featured-article-link:link, .featured-article-link:visited { position: absolute; bottom: 0; margin-bottom: 0; background-color: rgba(255,255,255,.8); color: #000; font-size: 1.1em; padding: 7px; } .featured-article-link:hover, .featured-article-link:focus { color: #654d31; text-decoration: none; border-bottom: none; } ``` Run `yarn encore ` to regenerate assets. At this point you can add a new Content Scheduler block to your page and fill it with content to see how it works. > **Tip: Tip** > > If you don't see the featured block template, you may need to clear the cache (using `php bin/console cache:clear`) and/or reload the app. Go back to editing the Home page and drag a Content Scheduler block from the pane on the right to the main zone in the layout, above the Content List block. Select the block and click the Block Settings icon. Set the Limit to three and click Select Content. Navigate to the "All Articles" folder and select the articles you had created and confirm. *[Image: Selecting Articles for the Schedule Block]* Accept the suggested airtime and click **Submit**. Now click the Airtime button next to one of the Articles and choose a time in the future. This article is listed in the queue. *[Image: Content Scheduler with scheduled content]* Publish the page. Now open the Timeline at the top of the screen. You can move the slider to different times and preview what the Content Scheduler block looks like at different hours. Content is shown when you move the slider to the point when it airs. > **Tip: Tip** > > At this point you have configured the Content Scheduler block to work with Articles only. If you try to add Content of any other type, you can see an error. This is because there is no `featured` view for content other than Articles defined at the moment. *[Image: Front page after adding Featured Block]* # Step 4 — Create a custom block Editions: Experience This step guides you through creating a custom block. The custom block displays a randomly chosen content item from a selected folder. To create a custom block from scratch you need four elements: - block configuration - a template - a listener - the listener registered as a service ### Block configuration In `config/packages/ibexa_fieldtype_page.yaml` add the following block under the `blocks` key: ``` random: name: Random block thumbnail: /assets/images/blocks/random_block.svg#random views: random: template: blocks/random/default.html.twig name: Random Content Block View attributes: parent: type: embed name: Parent validators: not_blank: message: You must provide value regexp: options: pattern: '/[0-9]+/' message: Choose a content item ``` This configuration defines one attribute, `parent`. Use it to select the folder containing tips. ### Block template You also need to create the block template, `templates/blocks/random/default.html.twig`: ```

{{ 'Tip of the Day'|trans }}

{{ ibexa_content_name(randomContent) }}
{{ ibexa_render_field(randomContent, 'body') }}
``` ### Block listener Block listener provides the logic for the block. It's contained in `src/Event/RandomBlockListener.php`: ``` 'onBlockPreRender', ]; } public function onBlockPreRender(PreRenderEvent $event): void { $blockValue = $event->getBlockValue(); /** @var \Ibexa\FieldTypePage\FieldType\Page\Block\Renderer\Twig\TwigRenderRequest $renderRequest */ $renderRequest = $event->getRenderRequest(); $parameters = $renderRequest->getParameters(); $contentIdAttribute = $blockValue->getAttribute('parent'); $location = $this->loadLocationByContentId((int) $contentIdAttribute->getValue()); $contents = $this->findContentItems($location); shuffle($contents); $parameters['randomContent'] = reset($contents); $renderRequest->setParameters($parameters); } private function findContentItems(Location $location): array { $query = new Query(); $query->query = new Criterion\LogicalAnd( [ new Criterion\ParentLocationId($location->id), new Criterion\Visibility(Criterion\Visibility::VISIBLE), ] ); $searchHits = $this->searchService->findContent($query)->searchHits; $contentArray = []; foreach ($searchHits as $searchHit) { $contentArray[] = $searchHit->valueObject; } return $contentArray; } private function loadLocationByContentId(int $contentId): Location { $contentInfo = $this->contentService->loadContentInfo($contentId); return $this->locationService->loadLocation($contentInfo->mainLocationId); } } ``` At this point the new custom block is ready to be used. You're left with the last cosmetic changes. First, the new Block has a broken icon in the **Page blocks** toolbox in page mode. This is because you haven't provided this icon yet. If you look back to the YAML configuration, you can see the icon file defined as `random_block.svg` (line 4). Download [the provided file](https://github.com/ibexa/documentation-developer/blob/5.0/code_samples/tutorials/page_tutorial_starting_point/public/assets/images/blocks/random_block.svg) and place it in `public/assets/images/blocks`. Finally, add some styling for the new block. Add the following to the end of the `assets/css/style.css` file: ``` /* Random block */ .random-block { border: 1px solid #83705a; border-radius: 5px; padding: 0 25px 25px 25px; margin-top: 15px; } .random-block h4 { font-variant: small-caps; font-size: 1.2em; } .random-block h5 { font-size: 1.2em; } .random-block-text { font-size: .85em; } ``` Run `yarn encore ` to regenerate assets. Go back to editing the front page. Drag a Random Block from the **Page blocks** toolbox on the right to the page's side column. Access the block's settings and choose the "All Tips" folder from the menu. Save and publish all the changes. Refresh the home page. The Tip of the Day block displays a random Tip from the "Tips" folder. Refresh the page a few more times and you can see the tip change randomly. *[Image: Random Block with a Tip]* To learn more about custom Page Builder blocks, see [Create custom page block](https://doc.ibexa.co/en/latest/content_management/pages/create_custom_page_block/index.md). # Step 5 — Create a newsletter form Editions: Experience The final step of this tutorial assists you in adding to the home page a Form block for signing up to a newsletter. > **Caution: Known limitation** > > To have multiple instances of the same form on one page, create several identical form blocks. Otherwise, you may encounter issues with submitting data from all forms at the same time. ### Add a Form block Start with creating a Form content item. In the main menu, go to **Content** -> **Forms**, click **Create content** and select **Form**. Provide the title, for example, "Sign up for Newsletter" and click **Build form**. In the Form Builder, add and configure (using the **Basic** and **Validation** tabs) the following form fields: | Form field | Name | Required | Additional properties | | ----------------- | ------------- | -------- | ---------------------------------------------------- | | Single line input | Name | yes | Minimum length = 3 | | Single line input | Surname | no | Minimum length = 3 | | Dropdown | Select topic | yes | Options:- News- Tips - Articles | | Email | Email address | yes | — | | Captcha | CAPTCHA | — | — | | Button | Sign up! | — | Action: Show a messageMessage to display: Thank you! | The configuration should look like this: *[Image: Adding fields to Newsletter Form]* When you add all the fields, save the form and click **Publish**. Now you can edit the front page and add a Form block below the Random block. Edit the block and select the form you created. Click **Submit**. The Page should refresh with the Form block. *[Image: Newsletter Form Block]* It clearly differs from the page design, so you also need to customize the block's layout. ### Change the block template First, add a new template for the Form block to align it with the Random block design. Create a `newsletter.html.twig` file in `templates/blocks/form/`: ```
{{ ibexa_http_cache_tag_relation_location_ids(locationId) }} {{ render(controller('ibexa_content::viewAction', { 'contentId': contentId, 'locationId': locationId, 'viewType': 'embed' })) }}
``` This template extends the default block layout by adding an additional class (line 1) that shares CSS styling with the Random block. Append the new template to the block by adding it to `config/packages/ibexa_fieldtype_page.yaml`. Add the following configuration under the `blocks` key at the same level as other block names, for example, `random`: ``` form: views: default: template: blocks/form/newsletter.html.twig name: Newsletter Form View ``` Now you have to apply the template to the block. Go back to editing the page. Edit the Form block again. In the **Design** tab, select the **Newsletter Form View** and click **Submit**. The block remains unchanged, but the results are visible when you add CSS styling. ### Change the field template At this point, you need to change the field template. This results in alternating the position and design of the Form fields. Create a `form_field.html.twig` file in `templates/fields/`: ``` {% block ibexa_form_field %} {% set formValue = field.value.getForm() %} {% if formValue %} {% set form = formValue.createView() %} {% form_theme form 'bootstrap_4_layout.html.twig' %} {% apply spaceless %} {% if not ibexa_field_is_empty(content, field) %} {{ form(form) }} {% endif %} {% endapply %} {% endif %} {% endblock %} ``` Next, assign the template to the page. In `config/packages/views.yaml`, at the same level as `page_layout`, add: ``` field_templates: - { template: fields/form_field.html.twig, priority: 30 } ``` Clear the cache by running `bin/console cache:clear` and refresh the page to see the results. ### Configure the Form field Before applying the final styling of the block, you need to configure the [CAPTCHA field](https://doc.ibexa.co/en/latest/content_management/forms/work_with_forms/#captcha-field). In `config/packages`, add a `gregwar_captcha.yaml` file with the following configuration: ``` gregwar_captcha: width: 150 invalid_message: Please, enter again. length: 4 ``` The configuration resizes the CAPTCHA image (line 2), changes the error message (line 3), and shortens the authentication code (line 4). ### Add stylesheet The remaining step in configuring the block is adding CSS styling. Add the following code to `assets/css/style.css`: ``` /* Newsletter Form block */ .block-form { border: 1px solid #8b7f7b; border-radius: 5px; padding: 0 25px 25px 25px; margin-top: 15px; } .block-form .ibexa_string-field { display: inline-block; font-variant: small-caps; font-size: 1.2em; width: 100%; text-align: right; padding-top: 20px; } .block-form .col-form-label { display: none; } .block-form .form-group { font-variant: small-caps; font-size: 1em; margin-top: 12px; } .captcha_image { padding-left: 10px; padding-bottom: 10px; } .captcha_reload { font-variant: all-small-caps; padding-left: 5px; } .btn-primary { background-color: rgb(103,85,80); border-color: rgb(103,85,80); margin-top: 15px; margin-bottom: -20px; } .block-form h3 { font-variant: all-small-caps; text-align: center; } ``` Reinstall the assets and clear the cache by running the following commands: ``` yarn encore php bin/console cache:clear ``` Your newsletter form block is ready. *[Image: Newsletter Form Block]* Refresh the page and enter a couple of mock submissions. ### Manage the submissions You can view all submissions in the back office. Go to **Forms** page. From the content tree, select the Form and click the **Submissions** tab. There, after selecting submission(s), click **Download submissions** or **Delete submission**. To see details about a submission, click the view icon. *[Image: Collect Form Submissions]* For more information, see [viewing form results](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/work_with_forms/#view-results). ## Congratulations! You have finished the tutorial and created your first customized page. You have learned how to: - Create and customize a Page - Make use of existing blocks and adapt them to your needs - Plan content airtime using the Content Scheduler block - Create custom blocks - Use Form Builder and configure your form - Apply custom styling to blocks *[Image: Final result of the tutorial]* # Creating a Point 2D field type This tutorial covers the creation and development of a custom Ibexa DXP [field type](https://doc.ibexa.co/en/latest/content_management/field_types/create_custom_generic_field_type/index.md). The Generic field type is a powerful extension point. It enables you to build complex solutions on a ready-to-go field type template. Field types are responsible for: - Storing data, either using the native storage engine mechanisms or specific means - Validating input data - Making the data searchable (if applicable) - Displaying fields For more information, see [field type documentation](https://doc.ibexa.co/en/latest/content_management/field_types/field_types/index.md). It describes how each component of a field type interacts with the various layers of the system, and how to implement them. ## Intended audience This tutorial is aimed at developers who are familiar with Ibexa DXP and are comfortable with operating in PHP and Symfony. ## Content of the tutorial This tutorial shows you how to use the Generic field type as a template for a custom field type. You: - create a custom Point 2D field type with two coordinates as input, for example '4,5' - register the new field type as a service and define its template - add basic validation to your Point 2D - add data migration to the field type so you're able to change its output ## Steps In this tutorial you go through the following steps: - [1. Implement the Point 2D Value class](https://doc.ibexa.co/en/latest/tutorials/generic_field_type/1_implement_the_point2d_value_class/index.md) - [2. Define the Point 2D field type](https://doc.ibexa.co/en/latest/tutorials/generic_field_type/2_define_point2d_field_type/index.md) - [3. Create form for editing field type](https://doc.ibexa.co/en/latest/tutorials/generic_field_type/3_create_form_for_point2d/index.md) - [4. Introduce a template](https://doc.ibexa.co/en/latest/tutorials/generic_field_type/4_introduce_a_template/index.md) - [5. Add a new Point 2D field](https://doc.ibexa.co/en/latest/tutorials/generic_field_type/5_add_a_field/index.md) - [6. Implement Point 2D settings](https://doc.ibexa.co/en/latest/tutorials/generic_field_type/6_settings/index.md) - [7. Add basic validation](https://doc.ibexa.co/en/latest/tutorials/generic_field_type/7_add_a_validation/index.md) - [8. Data migration between field type versions](https://doc.ibexa.co/en/latest/tutorials/generic_field_type/8_data_migration/index.md) # Step 1 - Implement the Point 2D Value class ## Project installation To start the tutorial, you need to make a clean Ibexa DXP installation. Follow the guide for your system to [install Ibexa DXP](https://doc.ibexa.co/en/latest/getting_started/install_ibexa_dxp/index.md), [configure a server](https://doc.ibexa.co/en/latest/getting_started/requirements/index.md), and [start the web server](https://doc.ibexa.co/en/latest/getting_started/install_ibexa_dxp/#use-phps-built-in-server). Remember to install using the `dev` environment. Open your project with a clean installation and create the base directory for a new Point 2D field type in `src/FieldType/Point2D`. ## The Value class The Value class of a field type is by design very simple. It's used to represent an instance of the field type within a content item. Each field presents its data using an instance of the Type's Value class. For more information about field type Value, see [Value handling](https://doc.ibexa.co/en/latest/content_management/field_types/type_and_value/#value-handling). > **Tip: Tip** > > According to the convention, the class representing field type Value should be named `Value` and should be placed in the same namespace as the Type definition. > **Caution: Simple hash values** > > A simple hash value always means an array of scalar values and/or nested arrays of scalar values. To avoid issues with format conversion, don't use objects inside the simple hash values. The Point 2D Value class contains: - private properties, used to store the actual data - an implementation of the `__toString()` method, required by the Value interface By default, the constructor from `FieldType\Value` is used. The Point 2D is going to store two elements (coordinates for point 2D): - `x` value - `y` value At this point, it doesn't matter where they're stored. You want to focus on what the field type exposes as an API. `src/FieldType/Point2D/Value.php` should have the following properties: ``` public function __construct( private ?float $x = null, private ?float $y = null ) { } ``` A Value class must also implement the `Ibexa\Contracts\Core\FieldType\Value` interface. To match the `FieldType\Value` interface, you need to implement `__toString()` method. You also need to add getters and setters for `x` and `y` properties. This class represents the point 2D. The final code should look like this: ``` x; } public function setX(?float $x): void { $this->x = $x; } public function getY(): ?float { return $this->y; } public function setY(?float $y): void { $this->y = $y; } public function __toString(): string { return "({$this->x}, {$this->y})"; } } ``` # Step 2 - Define the Point 2D field type ## The Type class The Type contains logic of the field type: validating data, transforming from various formats, describing the validators, and more. In this example Point 2D field type extends the `Ibexa\Contracts\Core\FieldType\Generic\Type` class. For more information about the Type class of a field type, see [Type class](https://doc.ibexa.co/en/latest/content_management/field_types/type_and_value/#type-class). ## Field type identifier First, create `src/FieldType/Point2D/Type.php`. Add a `getFieldTypeIdentifier()` method to it. The new method returns the string that **uniquely** identifies your field type, in this case `point2d`: ``` add('x', NumberType::class); $builder->add('y', NumberType::class); } } ``` ## Add a Form Mapper Interface The FormMapper adds the field definitions into Symfony forms using the `add()` method. The `FieldValueFormMapperInterface` provides an edit form for your field type in the administration interface. For more information about the FormMappers, see [field type form and template](https://doc.ibexa.co/en/latest/content_management/field_types/form_and_template/index.md). First, implement a `FieldValueFormMapperInterface` interface (`Ibexa\Contracts\ContentForms\FieldType\FieldValueFormMapperInterface`) to field type definition in the `src/FieldType/Point2D/Type.php`. Next, implement a `mapFieldValueForm()` method and invoke `FormInterface::add` method with the following arguments (highlighted lines): - Name of the property the field value maps to: `value` - Type of the field: `Point2DType::class` - Custom options: `required` and `label` Final version of the Type class should have the following statements and functions: ``` getFieldDefinition(); $fieldForm->add('value', Point2DType::class, [ 'required' => $definition->isRequired, 'label' => $definition->getName(), ]); } } ``` Finally, add a `configureOptions` method and set default value of `data_class` to `Value::class` in `src/Form/Type/Point2DType.php`. It allows your form to work on this object. ``` add('x', NumberType::class); $builder->add('y', NumberType::class); } public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'data_class' => Value::class, ]); } } ``` ## Add a new tag Next, add the `ibexa.admin_ui.field_type.form.mapper.value` tag to `config/services.yaml`: ``` App\FieldType\Point2D\Type: tags: - { name: ibexa.field_type, alias: point2d } - { name: ibexa.admin_ui.field_type.form.mapper.value, fieldType: point2d } ``` # Step 4 - Introduce a template ## Point 2D template To display data from the field type, you need to create and register a template for it. Each field type template receives a set of variables that can be used to achieve the desired goal. In this case the most important variable is the `field`, an instance of `Ibexa\Contracts\Core\Repository\Values\Content\Field`. In addition to its own metadata (for example, `id` or `fieldDefIdentifier`), it exposes the field Value through the `value` property. Remember that field type templates can be overridden to tweak what is displayed and how. For more information, see [field type templates](https://doc.ibexa.co/en/latest/content_management/field_types/form_and_template/#content-view-templates). First, create a `point2d_field.html.twig` template in the `templates` directory. It defines the default display of a Point 2D. Your basic template for Point 2D should look like this: ``` {% block point2d_field %} ({{ field.value.getX() }}, {{ field.value.getY() }}) {% endblock %} ``` ## Template mapping Next, provide the template mapping in `config/packages/ibexa.yaml`: ``` ibexa: system: default: field_templates: - { template: 'point2d_field.html.twig', priority: 0 } ``` # Step 5 - Add a new Point 2D field All actions in this step are done in the admin interface also called the back office. Go to the admin interface (`/admin`) and log in with the default username: `admin` using the password specified during installation. ## Add new content type In the back office, the main menu, go to the **Content types** page. Under **Content** category, create a new content type: *[Image: Creating new content type]* New content type should have the following settings: - **Name:** Point 2D - **Identifier:** point_2d - **Fields:** point2d.name *[Image: Adding new field]* Next, define **point2d** with the following fields: | Field type | Name | Identifier | Required | Translatable | | ---------- | -------- | ---------- | -------- | ------------ | | point2d | Point 2D | `point_2d` | yes | no | *[Image: Defining Point 2D]* Save everything and go back to the **Content/Content structure** tab. ## Create your content In **Content structure**, select **Create content**. There, under **Content**, you should see Point 2D content type you added. Click it to create new content. *[Image: Selecting Point 2D from sidebar]* Here, you can fill in coordinates of your point, for example, 3, 5. Provided coordinates are used as a title for a new point. *[Image: Creating Point 2D]* Click **Publish**. Now, you should see a new **(3,5)** point in the content tree. > **Tip: Tip** > > If you cannot see the results or encounter an error, clear the cache and reload the application. *[Image: New Point 2D]* # Step 6 - Implement Point 2D settings Implementing settings enables you to define the format for displaying the field on the page. To do so, create the `format` field where you're able to change the way coordinates for Point 2D are displayed. ## Define field type format In this step you create the `format` field for Point 2D coordinates. To do that, you need to define a `SettingsSchema` definition. You also specify coordinates as placeholder values `%x%` and `%y%`. Open `src/FieldType/Point2D/Type.php` and add a `getSettingsSchema` method according to the following code block: ``` [ 'type' => 'string', 'default' => '(%x%, %y%)', ], ]; } public function mapFieldValueForm(FormInterface $fieldForm, FieldData $data): void { $definition = $data->getFieldDefinition(); $fieldForm->add('value', Point2DType::class, [ 'required' => $definition->isRequired, 'label' => $definition->getName(), ]); } } ``` ## Add a format field In this part you define and implement the edit form for your field type. Define a `Point2DSettingsType` class and add a `format` field in `src/Form/Type/Point2DSettingsType.php`: ``` add('format', TextType::class); } } ``` ## FieldDefinitionFormMapper Interface Now, enable the user to add the coordinates which are validated. In `src/FieldType/Point2D/Type.php` you: - implement the `FieldDefinitionFormMapperInterface` interface - add a `mapFieldDefinitionForm` method at the end that defines the field settings ``` add('fieldSettings', Point2DSettingsType::class, [ 'label' => false, ]); } } ``` Complete Type.php code ``` [ 'type' => 'string', 'default' => '(%x%, %y%)', ], ]; } public function mapFieldValueForm(FormInterface $fieldForm, FieldData $data): void { $definition = $data->getFieldDefinition(); $fieldForm->add('value', Point2DType::class, [ 'required' => $definition->isRequired, 'label' => $definition->getName(), ]); } public function mapFieldDefinitionForm(FormInterface $fieldDefinitionForm, FieldDefinitionData $data): void { $fieldDefinitionForm->add('fieldSettings', Point2DSettingsType::class, [ 'label' => false, ]); } } ``` ## Add a new tag Next, add `FieldDefinitionFormMapper` as an extra tag definition for `App\FieldType\Point2D\Type` in `config/services.yaml`: ``` App\FieldType\Point2D\Type: tags: - { name: ibexa.field_type, alias: point2d } - { name: ibexa.admin_ui.field_type.form.mapper.value, fieldType: point2d } - { name: ibexa.admin_ui.field_type.form.mapper.definition, fieldType: point2d } ``` ## Field type definition To be able to display the new `format` field, you need to add a template for it. Create `templates/point2d_field_type_definition.html.twig`: ``` {% block point2d_field_definition_edit %}
{{- form_label(form.fieldSettings.format) -}} {{- form_errors(form.fieldSettings.format) -}} {{- form_widget(form.fieldSettings.format) -}}
{% endblock %} ``` ### Add configuration for the format field Next, provide the template mapping in `config/packages/ibexa.yaml`: ``` ibexa: system: default: field_templates: - { template: 'point2d_field.html.twig', priority: 0 } fielddefinition_edit_templates: - { template: 'point2d_field_type_definition.html.twig', priority: 0 } ``` ## Redefine template Finally, redefine the Point 2D template, so it accommodates the new `format` field. In `templates/point2d_field.html.twig` replace the content with: ``` {% block point2d_field %} {{ fieldSettings.format|replace({ '%x%': field.value.x, '%y%': field.value.y }) }} {% endblock %} ``` ## Edit the content type Now, in the back office, you can go to **Content types** and see the results of your work by editing the Point 2D content type. > **Tip: Tip** > > If you cannot see the results or encounter an error, clear the cache and reload the application. Add new format `(%x%, %y%)` in the **Format** field as shown in the screen below. *[Image: Point 2D definition with format field]* # Step 7 - Add basic validation To provide basic validation that ensures both coordinates are provided, add assertions to the `src/FieldType/Point2D/Value.php`: ``` use Symfony\Component\Validator\Constraints as Assert; final class Value implements ValueInterface { /** * @var float|null * * @Assert\NotBlank() */ private $x; /** * @var float|null * * @Assert\NotBlank() */ private $y; // ... } ``` As a result, if a user tries to publish the Point 2D with one value, they receive an error message. *[Image: Point 2D validation]* # Step 8 - Data migration between field type versions Adding data migration enables you to change the output of the field type to fit your current needs. This process is important when a field type needs to be compared for sorting and searching purposes. Serialization allows changing objects to array by normalizing them, and then to the selected format by encoding them. In reverse, deserialization changes different formats into arrays by decoding and then denormalizing them into objects. For more information on Serializer Components, see [Symfony documentation](https://symfony.com/doc/7.4/serializer.html). ## Normalization First, you need to add support for normalization in a `src/Serializer/Point2D/ValueNormalizer.php`: ``` */ public function normalize($object, ?string $format = null, array $context = []): array { return [ $object->getX(), $object->getY(), ]; } public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool { return $data instanceof Value; } public function getSupportedTypes(?string $format): array { return [ Value::class => true, ]; } } ``` ## Add Normalizer definition Next, add the `ValueNormalizer` service definition to the `config/services.yaml` with a `serializer.normalizer` tag: ``` services: App\Serializer\Point2D\ValueNormalizer: tags: - { name: serializer.normalizer } ``` ## Backward compatibility To accept old versions of the field type you need to add support for denormalization in a `src/Serializer/Point2D/ValueDenormalizer.php`: ``` true, ]; } } ``` ## Add Denormalizer definition Next, add the `ValueDenormalizer` service definition to `config/services.yaml` with a `serializer.denormalizer` tag: ``` services: App\Serializer\Point2D\ValueDenormalizer: tags: - { name: serializer.denormalizer } ``` ## Change format on the fly To change the format on the fly, you need to replace the constructor in `src/FieldType/Point2D/Value.php`: ``` public function __construct(array $coords = []) { if (!empty($coords)) { $this->x = $coords[0]; $this->y = $coords[1]; } } ``` Now you can change the internal representation format of the Point 2D field type. # API # API Ibexa DXP is an API-first product and provides APIs to handle content and repository information. - [REST API usage](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/api/rest_api/rest_api_usage/rest_api_usage/): The REST API covers objects in the Ibexa DXP Repository with regular and custom HTTP methods, such as GET or PUBLISH, and HTTP headers. - [PHP API](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/api/php_api/php_api/): Public PHP API exposes the Repository in a number of services and allows creating, reading, updating, managing, and deleting objects. - [GraphQL](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/api/graphql/graphql/): GraphQL enables making concise, readable requests to Ibexa DXP APIs. # PHP API The [public PHP API](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/index.md) enables you to interact with Ibexa DXP's Repository and content model from your PHP code. You can use it to create, read, update, manage, and delete all objects available in Ibexa DXP, namely content and related objects such as sections, locations, content types, or languages. The PHP API is built on top of a layered architecture, including a persistence SPI that abstracts storage. Using the API ensures that your code is forward compatible with future releases based on other storage engines. ## Using API services The API provides access to content, user, content types, and other features through various services. The full list of available services covers: - [BatchOrderService](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Cart-BatchOrderServiceInterface.html) - [CorporateAccountService](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-CorporateAccount-Service-CorporateAccountService.html) (recommended for company creation) - [CompanyService](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-CorporateAccount-Service-CompanyService.html) - [ContentService](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-ContentService.html) - [ContentTypeService](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-ContentTypeService.html) - [FieldTypeService](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-FieldTypeService.html) - [InvitationService](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-User-Invitation-InvitationService.html) - [LanguageService](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-LanguageService.html) - [LocationService](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-LocationService.html) - [MemberService](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-CorporateAccount-Service-MemberService.html) - [NotificationService](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-NotificationService.html) - [ObjectStateService](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-ObjectStateService.html) - [RoleService](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-RoleService.html) - [SearchService](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-SearchService.html) - [SectionService](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-SectionService.html) - [ShippingAddressService](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-CorporateAccount-Service-ShippingAddressService.html) - [SpreadsheetProcessorInterface](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Cart-FileProcessor-SpreadsheetProcessorInterface.html) - [TaxonomyService](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Taxonomy-Service-TaxonomyServiceInterface.html) - [TranslationService](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-TranslationService.html) - [TrashService](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-TrashService.html) - [URLAliasService](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-URLAliasService.html) - [URLService](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-URLService.html) - [URLWildcardService](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-URLWildcardService.html) - [UserPreferenceService](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-UserPreferenceService.html) - [UserService](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-UserService.html) You can access the PHP API by injecting relevant services into your code: - By using [auto-wiring](https://symfony.com/doc/7.4/service_container/autowiring.html), and the service class name in the `Ibexa\Contracts` namespace (see `bin/console debug:autowiring | grep Ibexa.Contracts`). - By using [service parameters](https://symfony.com/doc/7.4/service_container.html#service-parameters), and service aliases (see `bin/console debug:autowiring | grep ibexa.api`). - By using the repository's `get[ServiceName]()` methods, for example, [`Repository::getContentService()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Repository.html#method_getContentService), or [`getUserService()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Repository.html#method_getUserService). (Prefer injecting several Repository's dedicated services instead of the whole Repository if the Repository itself isn't needed.) > **Caution: Caution** > > The PHP API's services can be accessed with `Ibexa\Bundle\Core\Controller::getRepository()` by extending it from a [custom controller](https://doc.ibexa.co/en/latest/templating/queries_and_controllers/controllers/index.md), but such approach isn't recommended, and you should prefer dependency injection. ## Value objects The services provide interaction with read-only value objects from the [`Ibexa\Contracts\Core\Repository\Values`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/namespaces/ibexa-contracts-core-repository-values.html) namespace. Those objects are divided into sub-namespaces, such as [`Content`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/namespaces/ibexa-contracts-core-repository-values-content.html), [`User`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/namespaces/ibexa-contracts-core-repository-values-user.html) or [`ObjectState`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/namespaces/ibexa-contracts-core-repository-values-objectstate.html). Each sub-namespace contains a set of value objects, such as [`Content\Content`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Content.html) or [`User\Role`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-User-Role.html). Value objects come with their own properties, such as `$content->id` or `$location->hidden`, and with methods that provide access to more related information, such as [`Content\Relation::getSourceContentInfo()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Relation.html#method_getSourceContentInfo) or [`User\Role::getPolicies()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-User-Role.html#method_getPolicies). ### Creating and updating objects Value objects fetch data from the repository and are read-only. To create and modify repository values, use data structures, such as [`ContentService::newContentCreateStruct()`](https://github.com/ibexa/core/blob/v4.6.6/src/contracts/Repository/ContentService.php#L572) or [`LocationService::newLocationUpdateStruct()`](https://github.com/ibexa/core/blob/v4.6.6/src/contracts/Repository/LocationService.php#L238). ### Value info objects Some complex value objects have an `Info` counterpart, for example [`ContentInfo`](https://github.com/ibexa/core/blob/main/src/contracts/Repository/Values/Content/ContentInfo.php) for [`Content`](https://github.com/ibexa/core/blob/main/src/contracts/Repository/Values/Content/Content.php). These objects provide you with lower-level information. For instance, `ContentInfo` contains `currentVersionNo` or `remoteId`, while `Content` enables you to retrieve fields, content type, or previous versions. > **Note: Note** > > The public PHP API value objects should not be serialized. > > Serialization of value objects, for example, `Ibexa\Contracts\Core\Repository\Values\Content\ContentInfo` / `Ibexa\Contracts\Core\Repository\Values\Content\VersionInfo` or `Ibexa\Contracts\Core\Repository\Values\Content\Location` results in memory limit exceeded error. ## Authentication One of the responsibilities of the repository is user authentication. Every action is executed *as* a user. When you use the PHP API, authentication is performed in three ways: - [automatically in the back office](#back-office-authentication) - [by using `sudo()`](#using-sudo) - by [setting the Repository user](#setting-the-repository-user) ### Back office authentication When actions are performed through the back office, they're executed as the logged-in user. This user's permissions affects the behavior of the repository. The user may, for example, not be allowed to create content, or view a particular section. ### Using `sudo()` To skip permission checks, you can use the `sudo()` method. It allows API execution to be performed with full access, sand-boxed. You can use this method to perform an action that the current user doesn't have permissions for. For example, to [hide a Location](https://doc.ibexa.co/en/latest/content_management/content_api/managing_content/#hiding-and-revealing-locations), use: ``` use Ibexa\Contracts\Core\Repository\Repository; //... $hiddenLocation = $repository->sudo(function (Repository $repository) use ($location) { return $repository->getLocationService()->hideLocation($location); }); ``` ### Setting the repository user In a command line script, the repository runs as if executed by the anonymous user. While [using `sudo()`](#using-sudo) is the recommended option, you can also set the current user to a user with necessary permissions to achieve the same effect. To identify as a different user, you need to use the `UserService` together with `PermissionResolver` (in the example `admin` is the login of the administrator user): ``` $user = $this->userService->loadUserByLogin('admin'); $this->permissionResolver->setCurrentUserReference($user); ``` > **Tip: Tip** > > [`Ibexa\Contracts\Core\Repository\PermissionService`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-PermissionService.html) can be injected to have a Service which provides both `PermissionResolver` and `PermissionCriterionResolver`. It supports auto-wiring. This isn't required in template functions or controller code, as the HTTP layer takes care of identifying the user, and automatically sets it in the repository. ## Exception handling PHP API uses [Exceptions](https://www.php.net/exceptions) to handle errors. Each API method may throw different exceptions, depending on what it does. It's good practice to cover every exception you expect to happen. For example if you're using a command which takes the content ID as a parameter, the ID may either not exist, or the referenced content item may not be visible to the user. Both cases should be covered with error messages: ``` try { // ... } catch (\Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException $e) { $output->writeln("No content with id $contentId found"); } catch (\Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException $e) { $output->writeln("Permission denied on content with id $contentId"); } ``` ## Service container Ibexa DXP uses the [Symfony service container](https://symfony.com/doc/7.4/service_container.html) for dependency resolution. [Symfony dependency injection](https://symfony.com/doc/7.4/components/dependency_injection.html) ensures that any required services are available in your custom code (for example, controllers) when you inject them into the constructor. Symfony service container uses service tags to dedicate services to a specific purpose. They're usually used for extension points. Ibexa DXP uses service tags to expose multiple features. For example, field types are tagged `ibexa.field_type`. > **Tip: Tip** > > For a list of all service tags exposed by Symfony, see its [reference documentation](https://symfony.com/doc/7.4/reference/dic_tags.html). # REST API usage The REST API in Ibexa DXP allows you to interact with an Ibexa DXP installation by using the HTTP protocol, following a [REST](https://en.wikipedia.org/wiki/Representational_state_transfer) interaction model. Each resource (URI) interacts with a part of the system (like content, users or search). Every interaction with the repository than you can do from back office or by using the [Public PHP API](https://doc.ibexa.co/en/latest/api/php_api/php_api/index.md) can also be done with the REST API. The REST API uses HTTP methods (such as `GET` and `PUBLISH`), and HTTP headers to specify the type of request. ## OpenAPI support The REST API is built on top of [API Platform](https://api-platform.com/docs/symfony/) and meets the [OpenAPI](https://www.openapis.org/) standard. You can download the OpenAPI specification in: - [YAML format](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/openapi.yaml) - [JSON format](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/openapi.json) You can also generate one for your project by running one of the commands below: ``` php bin/console ibexa:openapi --output=openapi.json # JSON output php bin/console ibexa:openapi --yaml --output=openapi.yaml # YAML output ``` Use the specification file with [available OpenAPI tools](https://tools.openapis.org/) to work faster with the API, for example, by generating libraries and clients for the API. > **Info: Info** > > In [Symfony's `dev` environment](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/environments/index.md), you can access a REST API reference generated for your project by visiting the `/api/ibexa/v2/doc` route in the browser. ## URIs The REST API is designed in such a way that the client can explore the Repository without constructing any URIs to resources. Starting from the [root resource](#rest-root), every response includes further links (`href`) to related resources. ### URI prefix [REST reference](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html), for the sake of readability, uses no prefixes in the URIs. In practice, the `/api/ibexa/v2` prefixes all REST hrefs. This prefix immediately follows the domain, and you can't use the [`URIElement` SiteAccess matcher](https://doc.ibexa.co/en/latest/multisite/siteaccess/siteaccess_matching/#urielement). If you need to the select a SiteAccess, see the [`X-Siteaccess` HTTP header](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_usage/rest_requests/#siteaccess). ### URI parameters URI parameters (query string) can be used on some resources. They usually serve as options or filters for the requested resource. As an example, the request below would paginate the results and return the first 5 relations for version 3 of the content item 59: ``` GET /content/objects/59/versions/3/relations?limit=5 HTTP/1.1 Accept: application/vnd.ibexa.api.RelationList+xml ``` #### Working with value objects IDs Resources that accept a reference to another resource expect the reference to be given as a REST URI, not a single ID. For example, the URI requesting a list of user groups assigned to the role with ID 1 is: ``` GET /api/ibexa/v2/user/groups?roleId=/api/ibexa/v2/user/roles/1 HTTP/1.1 ``` ### REST root The `/` root route is answered by a reference list with the main resource routes and media-types. It's presented in XML by default, but you can also switch to JSON output. ``` curl https://api.example.com/api/ibexa/v2/ curl -H "Accept: application/json" https://api.example.com/api/ibexa/v2/ ``` ### Country list Alongside regular Repository interactions, there is a REST service providing a list of countries with their names, [ISO-3166](https://en.wikipedia.org/wiki/ISO_3166) codes and International Dialing Codes (IDC). You can use it when presenting a country options list from any application. This country list's URI is `/services/countries`. The ISO-3166 country codes can be represented as: - two-letter code (alpha-2) — recommended as the general purpose code - three-letter code (alpha-3) — related to the country name - three-digit numeric code (numeric-3) — use it if you need to avoid using Latin script For details, see the [ISO-3166 glossary](https://www.iso.org/glossary-for-iso-3166.html). ## REST communication summary - A REST route (URI) leads to a REST controller action. A REST route is composed of the root prefix (`ibexa.rest.path_prefix: /api/ibexa/v2`) and a resource path (for example, `/content/objects/{contentId}`). - This controller action returns an `Ibexa\Rest\Value` descendant. - This controller action might use the `Request` to build its result according to, for example, GET parameters, the `Accept` HTTP header, or the request payload and its `Content-Type` HTTP header. - This controller action might wrap its return in a `CachedValue` which contains caching information for the reverse proxies. - The `Ibexa\Bundle\Rest\EventListener\ResponseListener` attached to the `kernel.view event` is triggered, and passes the request and the controller action's result to the `AcceptHeaderVisitorDispatcher`. - The `AcceptHeaderVisitorDispatcher` matches one of the `regexps` of an `ibexa.rest.output.visitor` service (an `Ibexa\Contracts\Rest\Output\Visitor`). The role of this `Output\Visitor` is to transform the value returned by the controller into XML or JSON output format. To do so, it combines an `Output\Generator` corresponding to the output format and a `ValueObjectVisitorDispatcher`. This `Output\Generator` is also adding the `media-type` attributes. - The matched `Output\Visitor` uses its `ValueObjectVisitorDispatcher` to select the right `ValueObjectVisitor` according to the fully qualified class name (FQCN) of the controller result. A `ValueObjectVisitor` is a service tagged `ibexa.rest.output.value_object.visitor` and this tag has a property `type` pointing a FQCN. - `ValueObjectVisitor`s recursively help to transform the controller result thanks to the abstraction layer of the `Generator`. - The `Output\Visitor` returns the `Response` to send back to the client. # REST requests ## Request method Depending on the HTTP method used, different actions are possible on the same resource. Example: | Action | Description | | --------------------------------------- | ------------------------------------------------------------------ | | `GET /content/objects/2/versions/3` | Fetches data about version #3 of content item #2 | | `PATCH /content/objects/2/versions/3` | Updates the version #3 draft of content item #2 | | `DELETE /content/objects/2/versions/3` | Deletes the (draft or archived) version #3 from content item #2 | | `COPY /content/objects/2/versions/3` | Creates a new draft version of content item #2 from its version #3 | | `PUBLISH /content/objects/2/versions/3` | Promotes the version #3 of content item #2 from draft to published | | `OPTIONS /content/objects/2/versions/3` | Lists all the methods usable with this resource, the 5 ones above | The following list of available methods gives an overview of the kind of action a method triggers on a resource, if available. For method action details per resource, see the [REST API reference](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html). | HTTP method | Status | Description | Safe | | -------------------------------------------------------------------- | -------- | ---------------------- | ---- | | [OPTIONS](https://datatracker.ietf.org/doc/html/rfc2616#section-9.2) | Standard | List available methods | Yes | | [GET](https://datatracker.ietf.org/doc/html/rfc2616#section-9.3) | Standard | Collect data | Yes | | [HEAD](https://datatracker.ietf.org/doc/html/rfc2616#section-9.4) | Standard | Check existence | Yes | | [POST](https://datatracker.ietf.org/doc/html/rfc2616#section-9.5) | Standard | Create an item | No | | [PATCH](https://datatracker.ietf.org/doc/html/rfc5789) | Custom | Update an item | No | | COPY | Custom | Duplicate an item | No | | [MOVE](https://datatracker.ietf.org/doc/html/rfc2518) | Custom | Move an item | No | | SWAP | Custom | Swap two locations | No | | PUBLISH | Custom | Publish an item | No | | [DELETE](https://datatracker.ietf.org/doc/html/rfc2616#section-9.7) | Standard | Remove an item | No | > **Note: Caution with custom HTTP methods** > > Using custom HTTP methods can cause issues with several HTTP proxies, network firewall/security solutions and simpler web servers. To avoid such issuess, REST API allows you to set these by using the HTTP header `X-HTTP-Method-Override` alongside the standard `POST` method instead of using a custom HTTP method. For example: `X-HTTP-Method-Override: PUBLISH` > > If applicable, both methods are always mentioned in the specifications. Unsafe methods require a CSRF token if [session-based authentication](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_authentication/#session-based-authentication) is used. ### OPTIONS method Any REST API URI responds to an `OPTIONS` request. The response contains an [`Allow` header](https://www.rfc-editor.org/rfc/rfc9110.html#name-allow), which lists the methods accepted by the resource. ``` curl -IX OPTIONS https://api.example.com/api/ibexa/v2/content/objects/1 ``` ``` OPTIONS /content/objects/1 HTTP/1.1 Host: api.example.com ``` ``` HTTP/1.1 200 OK Allow: PATCH,GET,DELETE,COPY ``` ``` curl -IX OPTIONS https://api.example.com/api/ibexa/v2/content/locations/1/2 ``` ``` OPTIONS /content/locations/1/2 HTTP/1.1 Host: api.example.com ``` ``` HTTP/1.1 200 OK Allow: GET,PATCH,DELETE,COPY,MOVE,SWAP ``` ## Request headers You can use the following HTTP headers with a REST request: - [`Accept`](https://datatracker.ietf.org/doc/html/rfc2616#section-14.1) describing the desired response type and format - [`Content-Type`](https://datatracker.ietf.org/doc/html/rfc2616#section-14.17) describing the payload type and format - [`X-Siteaccess`](#siteaccess) specifying the target SiteAccess - `X-HTTP-Method-Override` allowing to pass a custom method while using `POST` method as previously seen in [HTTP method](#request-method) - [`Destination`](#destination) specifying where to move an item - [`X-Expected-User`](#expected-user) specifying the user needed for the request execution Other headers related to authentication methods can be found in [REST API authentication](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_authentication/index.md). ### SiteAccess To specify a SiteAccess when communicating with the REST API, provide a custom `X-Siteaccess` header. Otherwise, the default SiteAccess is used. The following example shows what could be a SiteAccess called `restapi` dedicated to REST API accesses: ``` GET / HTTP/1.1 Host: api.example.com Accept: application/vnd.ibexa.api.Root+json X-Siteaccess: restapi ``` One of the principles of REST is that the same resource (such as content item, location, content type) should be unique. It allows caching your REST API with a reverse proxy such as Varnish. If the same resource is available in multiple locations, cache purging is noticeably more complex. This is why SiteAccess matching with REST isn't enabled at URL level (or domain). ### Media types On top of methods, HTTP request headers allow you to personalize the request's behavior. On every resource, you can use the `Accept` header to indicate which format you want to communicate in, JSON or XML. This header is also used to specify the response type you want the server to send when multiple types are available. - `Accept: application/vnd.ibexa.api.Content+xml` to get `Content` (full data, fields included) as **[XML](https://www.w3.org/XML/)** - `Accept: application/vnd.ibexa.api.ContentInfo+json` to get `ContentInfo` (metadata only) as **[JSON](https://www.json.org/)** Media types are also used with the [`Content-Type` header](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_usage/rest_responses/#content-type-header) to characterize a [request body](#request-body) or a [response body](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_usage/rest_responses/#response-body). See [Creating content with binary attachments](#creating-content-with-binary-attachments) below. Also see [Creating session](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_authentication/#creating-session) examples. If the resource only returns one media type, it's also possible to skip it and to specify the format with `application/xml` or `application/json`. A response indicates `href`s to related resources and their media types. ### Destination The `Destination` request header is the request counterpart of the `Location` response header. It's used for a `COPY`, `MOVE` or `SWAP` operation to indicate where the resource should be moved, copied to or swapped with by using the ID of the parent or target location. Examples of such requests are: - [copying a Content](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#managing-content-copy-content) - [moving a Location and its subtree](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#managing-content-move-subtree) - [swapping a Location with another](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#managing-content-swap-location) ### Expected user The `X-Expected-User` header specifies the user needed for the request execution. With this header, if the current username on server side isn't equal to `X-Expected-User` value, a `401 Unauthorized` error is returned. Without this header, the request is executed with the current user who might be unexpected (like the Anonymous user if a previous authentication has expired) and an ambiguous response might be returned as a success not informing about a wrong user. For example, it prevents a Content request to be executed with Anonymous user in the case of an expired authentication, and the response being a `200 OK` but missing content items due to access rights difference with the expected user. ## Request body You can pass some short scalar parameters in the URIs or as GET parameters, but other resources need heavier structured payloads passed in the request body, in particular the ones to create (`POST`) or update (`PATCH`) items. In the [REST API reference](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html), request payload examples are given when needed. One example is the [creation of an authentication session](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_authentication/#establishing-session). When creating a content item, a special payload is needed if the content type has some [Image](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/imagefield/index.md) or [BinaryFile](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/binaryfilefield/index.md) fields as files need to be attached. See the example of a [script uploading images](#creating-content-with-binary-attachments) below. When searching for content items (or locations), the query grammar is also particular. See the [Search section](#search-views) below. ### Creating content with binary attachments The example below is a command-line script to upload images. It's based on the [Symfony HttpClient](https://symfony.com/doc/7.4/http_client.html). This script: - receives an image path and optionally a name as command-line arguments, - uses the [HTTP basic authentication](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_authentication/#http-basic-authentication), if it's enabled, - creates a draft in the /Media/Images folder by posting (`POST`) data to [`/content/objects`](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Objects/operation/api_contentobjects_post), - and, publishes (`PUBLISH`) the draft through [`/content/objects/{contentId}/versions/{versionNo}`](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#managing-content-publish-a-content-version). **XML** ``` []\n"; exit(1); } if (!is_file($argv[1])) { echo "{$argv[1]} doesn't exist or is not a file.\n"; exit(2); } // URL to Ibexa DXP installation and its REST API $host = 'api.example.com'; $scheme = 'https'; $api = '/api/ibexa/v2'; $baseUrl = "{$scheme}://{$host}{$api}"; // User credentials $username = 'admin'; $password = 'publish'; // Targets $contentTypeId = 5; // "Image" $parentLocationPath = '1/43/51'; // "Media > Images" $sectionId = 3; // "Media" $fileName = basename($argv[1]); $fileSize = filesize($argv[1]); $fileContent = base64_encode(file_get_contents($argv[1])); $name = $argv[2] ?? $fileName; // Request payload $data = << eng-GB PATH ASC
name $name caption

$name

]]> image $fileName $fileSize
XML; $client = HttpClient::createForBaseUri($baseUrl, [ 'auth_basic' => [$username, $password], ]); $doc = new DOMDocument(); try { $response = $client->request('POST', "$baseUrl/content/objects", [ 'headers' => [ 'Content-Type: application/vnd.ibexa.api.ContentCreate+xml', 'Accept: application/vnd.ibexa.api.ContentInfo+xml', ], 'body' => $data, ]); } catch (HttpException\TransportExceptionInterface $exception) { echo "Client error: {$exception->getMessage()}\n"; exit(3); } if (201 !== $responseCode = $response->getStatusCode()) { if (!empty($response->getContent(false)) && $doc->loadXML($response->getContent(false)) && 'ErrorMessage' === $doc->firstChild->nodeName) { echo "Server error: {$doc->getElementsByTagName('errorCode')->item(0)->nodeValue} {$doc->getElementsByTagName('errorMessage')->item(0)->nodeValue}\n"; echo "\t{$doc->getElementsByTagName('errorDescription')->item(0)->nodeValue}\n"; exit(4); } $responseHeaders = $response->getInfo('response_headers'); $error = $responseHeaders[0] ?? $responseCode; echo "Server error: $error\n"; exit(5); } $doc->loadXML($response->getContent()); if ('Content' !== $doc->firstChild->nodeName || !$doc->firstChild->hasAttribute('id')) { echo "Response error: Unexpected response structure\n"; exit(6); } $contentId = $doc->firstChild->getAttribute('id'); try { $response = $client->request('PUBLISH', "$baseUrl/content/objects/$contentId/versions/1", [ 'headers' => [ 'Accept: application/xml', ], ]); } catch (HttpException\TransportExceptionInterface $exception) { echo "Client error: {$exception->getMessage()}\n"; exit(7); } if (204 !== $responseCode = $response->getStatusCode()) { if (!empty($response->getContent(false)) && $doc->loadXML($response->getContent(false)) && 'ErrorMessage' === $doc->firstChild->nodeName) { echo "Server error: {$doc->getElementsByTagName('errorCode')->item(0)->nodeValue} {{$doc->getElementsByTagName('errorMessage')->item(0)->nodeValue}\n"; echo "\t{$doc->getElementsByTagName('errorDescription')->item(0)->nodeValue}\n"; exit(8); } $responseHeaders = $response->getInfo('response_headers'); $error = $responseHeaders[0] ?? $responseCode; echo "Server error: $error\n"; exit(9); } echo "Success: Image content item created with ID $contentId and published.\n"; exit(0); ``` **JSON** ``` []\n"; exit(1); } if (!is_file($argv[1])) { echo "{$argv[1]} doesn't exist or is not a file.\n"; exit(2); } // URL to Ibexa DXP installation and its REST API $host = 'api.example.com'; $scheme = 'https'; $api = '/api/ibexa/v2'; $baseUrl = "{$scheme}://{$host}{$api}"; // User credentials $username = 'admin'; $password = 'publish'; // Targets $contentTypeId = 5; // "Image" $parentLocationPath = '1/43/51'; // "Media > Images" $sectionId = 3; // "Media" // Request payload $data = [ 'ContentCreate' => [ 'ContentType' => [ '_href' => "$api/content/types/$contentTypeId", ], 'mainLanguageCode' => 'eng-GB', 'LocationCreate' => [ 'ParentLocation' => [ '_href' => "$api/content/locations/$parentLocationPath", ], 'sortField' => 'PATH', 'sortOrder' => 'ASC', ], 'Section' => [ '_href' => "$api/content/sections/$sectionId", ], 'fields' => [ 'field' => [ [ 'fieldDefinitionIdentifier' => 'name', 'fieldValue' => $argv[2] ?? basename($argv[1]), ], [ 'fieldDefinitionIdentifier' => 'image', 'fieldValue' => [ // Original file name 'fileName' => basename($argv[1]), // File size in bytes 'fileSize' => filesize($argv[1]), // File content must be encoded as Base64 'data' => base64_encode(file_get_contents($argv[1])), ], ], ], ], ], ]; $client = HttpClient::createForBaseUri($baseUrl, [ 'auth_basic' => [$username, $password], ]); try { $response = $client->request('POST', "$baseUrl/content/objects", [ 'headers' => [ 'Content-Type: application/vnd.ibexa.api.ContentCreate+json', 'Accept: application/vnd.ibexa.api.ContentInfo+json', ], 'json' => $data, ]); } catch (HttpException\TransportExceptionInterface $exception) { echo "Client error: {$exception->getMessage()}\n"; exit(3); } if (201 !== $responseCode = $response->getStatusCode()) { try { $responseArray = $response->toArray(false); if (array_key_exists('ErrorMessage', $responseArray)) { echo "Server error: {$responseArray['ErrorMessage']['errorCode']} {$responseArray['ErrorMessage']['errorMessage']}\n"; echo "\t{$responseArray['ErrorMessage']['errorDescription']}\n"; exit(4); } } catch (HttpException\DecodingExceptionInterface) { } $responseHeaders = $response->getInfo('response_headers'); $error = $responseHeaders[0] ?? $responseCode; echo "Server error: $error\n"; exit(5); } $response = $response->toArray(); if (!(array_key_exists('Content', $response) && array_key_exists('_id', $response['Content']))) { echo "Response error: Unexpected response structure\n"; exit(6); } $contentId = $response['Content']['_id']; try { $response = $client->request('PUBLISH', "$baseUrl/content/objects/$contentId/versions/1", [ 'headers' => [ 'Accept: application/json', ], ]); } catch (HttpException\TransportExceptionInterface $exception) { echo "Client error: {$exception->getMessage()}\n"; exit(7); } if (204 !== $responseCode = $response->getStatusCode()) { try { $responseArray = $response->toArray(false); if (array_key_exists('ErrorMessage', $responseArray)) { echo "Server error: {$responseArray['ErrorMessage']['errorCode']} {$responseArray['ErrorMessage']['errorMessage']}\n"; echo "\t{$responseArray['ErrorMessage']['errorDescription']}\n"; exit(8); } } catch (HttpException\DecodingExceptionInterface) { } $responseHeaders = $response->getInfo('response_headers'); $error = $responseHeaders[0] ?? $responseCode; echo "Server error: $error\n"; exit(9); } echo "Success: Image content item created with ID $contentId and published.\n"; exit(0); ``` ### Search (`/views`) The `/views` route allows you to [search in the repository](https://doc.ibexa.co/en/latest/search/search/index.md). It works similarly to its [PHP API counterpart](https://doc.ibexa.co/en/latest/search/search_api/index.md). The model allows combining criteria using the logical operators `AND`, `OR` and `NOT`. Most [Search Criteria](https://doc.ibexa.co/en/latest/search/criteria_reference/search_criteria_reference/#search-criteria) are available in REST API. The suffix `Criterion` is added when used with REST API. Most [Sort Clauses](https://doc.ibexa.co/en/latest/search/sort_clause_reference/sort_clause_reference/#sort-clauses) are available too. They require no additional prefix or suffix. The search request has a `Content-Type: application/vnd.ibexa.api.ViewInput+xml` or `+json` header to specify the format of its body's payload. The root node is `` and it has two mandatory children: `` and ``. You can add `version=1.1` to the `Content-Type` header to support the distinction between `ContentQuery` and `LocationQuery` instead of `Query` which implicitly looks only for content items. The following examples search for `article` and `news` typed content items everywhere or for content items of all types directly under location `123`. All those content items must be in the `standard` section. **XML** ``` POST /views HTTP/1.1 Content-Type: application/vnd.ibexa.api.ViewInput+xml ``` ``` test article news 123 standard 10 0 ascending ``` **XML; 1.1** ``` POST /views HTTP/1.1 Content-Type: application/vnd.ibexa.api.ViewInput+xml; version=1.1 ``` ``` test article news 123 standard 10 0 ascending ``` **JSON** ``` POST /views HTTP/1.1 Content-Type: application/vnd.ibexa.api.ViewInput+json ``` ``` { "ViewInput": { "identifier": "test", "Query": { "Filter": { "AND": { "OR": { "ContentTypeIdentifierCriterion": [ "article", "news" ], "ParentLocationIdCriterion": 123 }, "SectionIdentifierCriterion": "standard" } }, "limit": "10", "offset": "0", "SortClauses": { "ContentName": "ascending" } } } } ``` **JSON; 1.1** ``` POST /views HTTP/1.1 Content-Type: application/vnd.ibexa.api.ViewInput+json; version=1.1 ``` ``` { "ViewInput": { "identifier": "test", "ContentQuery": { "Filter": { "AND": { "OR": { "ContentTypeIdentifierCriterion": [ "article", "news" ], "ParentLocationIdCriterion": 123 }, "SectionIdentifierCriterion": "standard" } }, "limit": "10", "offset": "0", "SortClauses": { "ContentName": "ascending" } } } } ``` > **Note: Note** > > In JSON, the structure for `ContentTypeIdentifierCriterion` with multiple values has a slightly different format as keys must be unique. In JSON, if there is only one item in `SortClauses`, it can be passed directly without an array to wrap it. You can omit logical operators. If Criteria are of mixed types, they're wrapped in an implicit `AND`. If they're of the same type, they're wrapped in an implicit `OR`. For example, the `AND` operator from previous example's `Filter` could be removed. **XML ExplicitAND** ``` article news 123 standard ``` **XML ImplicitAND** ``` article news 123 standard ``` **JSON ExplicitAND** ``` "Filter": { "AND": { "OR": { "ContentTypeIdentifierCriterion": [ "article", "news" ], "ParentLocationIdCriterion": 123 }, "SectionIdentifierCriterion": "standard" } }, ``` **JSON ImplicitAND** ``` "Filter": { "OR": { "ContentTypeIdentifierCriterion": [ "article", "news" ], "ParentLocationIdCriterion": 123 }, "SectionIdentifierCriterion": "standard" }, ``` # REST Responses ## Response code The following list of available HTTP response status codes gives an overview of the meaning of each code. For code details per resource, see the [REST API reference](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html). | Code | Message | Description | | ----- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `200` | OK | The resource has been found. | | `201` | Created | The request to create a new item has succeeded. The response `Location` header indicates where you can find the created item. | | `204` | No Content | The request has succeeded and there is no additional information in the response header or body (for example when publishing or deleting). | | `301` | Moved Permanently | The resource shouldn't be accessed this way. The response `Location` header indicates the proper way. | | `307` | Temporary Redirect | The resource is available at another URL considered as its main. The response `Location` header indicates this main URL. | | `400` | Bad Request | The input (payload) doesn't have the proper schema for the resource. | | `401` | Unauthorized | The user doesn't have the permission to make this request. | | `403` | Forbidden | The user has the permission but action can't be performed because of Repository logic (for example, when trying to create an item with an already existing ID or identifier, when attempting to update a version in another state than draft). | | `404` | Not Found | The requested object (or a request data like the parent of a new item) hasn't been found. | | `405` | Method Not Allowed | The requested resource doesn't support the HTTP verb that was used. | | `406` | Not Acceptable | The request's `Accept` header isn't supported. | | `409` | Conflict | The request is in conflict with another part of the repository (for example, trying to create a new item with an identifier already used). | | `415` | Unsupported Media Type | The request payload media type doesn't match the media type specified in the request header. | | `500` | Internal Server Error | The server encountered an unexpected condition, usually an exception, which prevents it from fulfilling the request, like database down, permissions or configuration error. | | `501` | Not Implemented | Returned when the requested method hasn't yet been implemented. For Ibexa DXP, most of users, user groups, content items, locations and content types have been implemented. Some of their methods, and other features, may return a 501. | ## Response headers A resource's response may contain metadata in its HTTP headers. > **Note: Note** > > For information about the `Allow` response header, see the [`OPTIONS` method](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_usage/rest_requests/#options-method). ### Content-Type header When a response contains an actual HTTP body, the `Content-Type` header specifies what the body contains. The `Content-Type` header's value is a [media type](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_usage/rest_requests/#media-types), like with the request `Accept` and `Content-Type` headers. For example, the first following request without an `Accept` header returns a default format indicated in the response `Content-Type` header, while the second request shows that the response is in the requested format. ``` GET /content/objects/52 HTTP/1.1 ``` ``` HTTP/1.1 200 OK Content-Type: application/vnd.ibexa.api.ContentInfo+xml ``` ``` GET /content/objects/52 HTTP/1.1 Accept: application/vnd.ibexa.api.Content+json ``` ``` HTTP/1.1 200 OK Content-Type: application/vnd.ibexa.api.Content+json ``` ### Accept-Patch header When available, the `Accept-Patch` tells how the received item could be modified with `PATCH`. The following examples also shows that the format (XML or JSON) is adapted: ``` GET /content/objects/52 HTTP/1.1 ``` ``` HTTP/1.1 200 OK Content-Type: application/vnd.ibexa.api.ContentInfo+xml Accept-Patch: application/vnd.ibexa.api.ContentUpdate+xml ``` ``` GET /content/objects/52 HTTP/1.1 Accept: application/vnd.ibexa.api.Content+json ``` ``` HTTP/1.1 200 OK Content-Type: application/vnd.ibexa.api.Content+json Accept-Patch: application/vnd.ibexa.api.ContentUpdate+json ``` Those example `Accept-Path` headers above indicate that the content could be modified by sending a [ContentUpdateStruct](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-ContentUpdateStruct.html) in XML or JSON. ### Location header For example, [creating content](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#managing-content-create-content-type) and [getting a content item's current version](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Objects/operation/api_contentobjects_contentIdcurrentversion_get) both send a `Location` header to provide you with the requested resource's ID. Those particular headers generally match a specific list of HTTP response codes. `Location` is mainly sent alongside `201 Created`, `301 Moved permanently`, `307 Temporary redirect responses`. In the following example, the content item's remote ID 34720ff636e1d4ce512f762dc638e4ac corresponds to the ID 52: ``` GET /content/objects?remoteId=34720ff636e1d4ce512f762dc638e4ac" HTTP/1.1 ``` ``` HTTP/1.1 307 Temporary Redirect Location: /content/objects/52 ``` In the following example, an erroneous slash has been added to demonstrate the 301 case: ``` GET /content/objects?remoteId=34720ff636e1d4ce512f762dc638e4ac" HTTP/1.1 ``` ``` HTTP/1.1 301 Moved Permanently Location: /content/objects?remoteId=34720ff636e1d4ce512f762dc638e4ac ``` cURL can follow those redirections. On CLI, there is the `--location` option (or its shorthand `-L`). In PHP, you can achieve the same effect with `CURLOPT_FOLLOWLOCATION`. The following command-line example follows the two redirections above and the `Accept` header is propagated: ``` curl --head --location --header "Accept: application/vnd.ibexa.api.Content+json" "https://api.example.com/api/ibexa/v2/content/objects/?remoteId=34720ff636e1d4ce512f762dc638e4ac" ``` ``` HTTP/1.1 200 OK Content-Type: application/vnd.ibexa.api.Content+json ``` ### Cross-origin [Cross-Origin Resource Sharing (CORS)](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) can allow the REST API to be reached from a page on another domain. For more information about CORS, see [WHATWG's CORS Protocol specification](https://fetch.spec.whatwg.org/#cors-protocol) and [Overview of CORS on developer.mozilla.org](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). CORS support is provided by the third party [nelmio/cors-bundle](https://packagist.org/packages/nelmio/cors-bundle). You can read more about it in [NelmioCorsBundle's README](https://github.com/nelmio/NelmioCorsBundle/blob/master/README.md). Using CORS isn't limited to REST API resources and can be used for any resource of the platform. The CORS bundle adds an `Access-Control-Allow-Origin` header to the response. #### Configuration To enable CORS, add regular expression for an allowed domain using the `.env` variable `CORS_ALLOW_ORIGIN`. For example, to allow the [JS test](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_usage/testing_rest_api/#js) to be executed alongside this page, you could add the following to an `.env` file (like the `.env.local`): `CORS_ALLOW_ORIGIN=^https?://doc.ibexa.co`. To add several domains, filter on URIs, or change the default (like not allowing all the methods), refer to [NelmioCorsBundle Configuration Documentation](https://symfony.com/bundles/NelmioCorsBundle/current/index.html#configuration) to learn how to edit `config/packages/nelmio_cors.yaml`. ## Response body The Response body is often a serialization in XML or JSON of an object as it could be retrieved using the Public PHP API. For example, the resource `/content/objects/52` with the `Accept: application/vnd.ibexa.api.ContentInfo+xml` header returns a serialized version of a [ContentInfo](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-ContentInfo.html) object. ``` curl https://api.example.com/content/objects/52 --header 'Accept: application/vnd.ibexa.api.ContentInfo+xml'; ``` ``` Ibexa Digital Experience Platform Ibexa Digital Experience Platform
2015-09-17T09:22:23+00:00 2015-09-17T09:22:23+00:00 eng-GB 1 true false PUBLISHED ``` The response body XML can contain two types of nodes: - Final nodes that fully give an information as a scalar value - Reference nodes which link to `href` where a new resource of a given `media-type` can be explored if you need to know more # Testing REST API A standard web browser isn't sufficient to fully test the API. You can, however, try opening the root resource with it, using the session authentication: `http://example.com/api/ibexa/v2/`. Depending on how your browser understands XML, it either downloads the XML file, or opens it in the browser. The following examples show how to interrogate the REST API with cURL, PHP or JS. ## CLI For examples of using `curl`, refer to: - [REST root](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_usage/rest_api_usage/#rest-root) - [OPTIONS method](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_usage/rest_requests/#options-method) - [Location header](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_usage/rest_responses/#location-header) - [ContentInfo body](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_usage/rest_responses/#response-body) ## PHP You can use [Symfony HttpClient](https://symfony.com/doc/7.4/http_client.html) to test REST API. Open a PHP shell in a terminal with `php -a` and copy-paste this code into it: ``` $resource = 'https://api.example.com/api/ibexa/v2/content/objects/52'; require 'vendor/autoload.php'; $client = Symfony\Component\HttpClient\HttpClient::create(); $response = $client->request('GET', $resource, [ 'headers' => ['Accept: application/vnd.ibexa.api.ContentInfo+json'], ]); var_dump($response->getStatusCode(), $response->getHeaders(), $response->toArray()); ``` `$resource` URI should be edited to address the right domain. On a freshly installed Ibexa DXP, `52` is the Content ID of the home page. If necessary, substitute `52` with the content ID of an item from your database. For a content creation example that uses PHP, see [Creating content with binary attachments](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_usage/rest_requests/#creating-content-with-binary-attachments) ## JS The REST API can help you implement JavaScript / AJAX interaction. The following example of an AJAX call retrieves `ContentInfo` (that is, metadata) for a content item. To test it, copy-paste this code into your browser console alongside a page from your website (to share the domain): **Fetch API** ``` const resource = '/api/ibexa/v2/content/objects/52'; fetch(resource, { headers: {'Accept': 'application/vnd.ibexa.api.ContentInfo+json'}, }).then((response) => { console.log(...response.headers); return response.json(); }).then((data) => { console.log(data); }); ``` **XMLHttpRequest** ``` const resource = '/api/ibexa/v2/content/objects/52'; const request = new XMLHttpRequest(); request.open('GET', resource, true); request.setRequestHeader('Accept', 'application/vnd.ibexa.api.ContentInfo+json'); request.onload = function () { console.log(request.getAllResponseHeaders(), JSON.parse(request.responseText)); }; request.send(); ``` On a freshly installed Ibexa DXP, `52` is the Content ID of the home page. If necessary, substitute `52` with the Content ID of an item from your database. You can edit the `resource` URI to address another domain, but [cross-origin requests](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_usage/rest_responses/#cross-origin) must be allowed first. # Adding custom media type In this example case, you pass a new media type in the `Accept` header of a GET request to `/content/locations/{locationPath}` route and its controller action (`Controller/Location::loadLocation`). By default, this resource takes an `application/vnd.ibexa.api.Location+xml` (or `+json`) `Accept` header. The following example adds the handling of a new media type `application/app.api.Location+xml` (or `+json`) `Accept` header to obtain a different response with the same controller. You need the following elements: - `ValueObjectVisitor` - to create the new response corresponding to the new media type - `ValueObjectVisitorDispatcher` - to have this `ValueObjectVisitor` used to visit the default controller result - `Output\Visitor` - service associating this new `ValueObjectVisitorDispatcher` with the new media type > **Note: Note** > > You can change the vendor name (from default `vnd.ibexa.api` to new `app.api` like in this example), or you can create a new media type in the default vendor (like `vnd.ibexa.api.Greeting` in the [Creating a new REST resource](https://doc.ibexa.co/en/latest/api/rest_api/extending_rest_api/creating_new_rest_resource/index.md) example). To do so, tag your new ValueObjectVisitor with `ibexa.rest.output.value_object.visitor` to add it to the existing `ValueObjectVisitorDispatcher`, and a new one isn't needed. This way, the `media-type` attribute is also easier to create, because the default `Output\Generator` uses this default vendor. This example presents creating a new vendor as a good practice, to highlight that this is custom extensions that isn't available in a regular Ibexa DXP installation. ## New `RestLocation` `ValueObjectVisitor` The controller action returns a `Values\RestLocation` object wrapped in `Values\CachedValue`. The new `ValueObjectVisitor` has to visit `Values\RestLocation` to prepare the new `Response`. To be accepted by the `ValueObjectVisitorDispatcher`, all new `ValueObjectVisitor` need to extend the abstract class `Output\ValueObjectVisitor`. In this example, this new `ValueObjectVisitor` extends the built-in `RestLocation` visitor to reuse it. This way, the abstract class is implicitly inherited. ``` startObjectElement to not have the XML Generator adding its own media-type attribute with the default vendor $generator->startHashElement('Location'); $generator->attribute( 'media-type', 'application/app.api.Location+' . strtolower((new \ReflectionClass($generator))->getShortName()) ); $generator->attribute( 'href', $this->router->generate( 'ibexa.rest.load_location', ['locationPath' => trim($data->location->pathString, '/')] ) ); parent::visit($visitor, $generator, $data); $visitor->visitValueObject(new URLAliasRefList(array_merge( $this->urlAliasService->listLocationAliases($data->location, false), $this->urlAliasService->listLocationAliases($data->location, true) ), $this->router->generate( 'ibexa.rest.list_location_url_aliases', ['locationPath' => trim($data->location->pathString, '/')] ))); $generator->endHashElement('Location'); } } ``` This new `ValueObjectVisitor` receives a new tag `app.rest.output.value_object.visitor` to be associated to the new `ValueObjectVisitorDispatcher` in the next step. This tag has a `type` property to associate the new `ValueObjectVisitor` with the type of value is made for. ``` services: #… App\Rest\ValueObjectVisitor\RestLocation: class: App\Rest\ValueObjectVisitor\RestLocation parent: Ibexa\Contracts\Rest\Output\ValueObjectVisitor arguments: $urlAliasService: '@ibexa.api.service.url_alias' tags: - { name: app.rest.output.value_object.visitor, type: Ibexa\Rest\Server\Values\RestLocation } ``` ## New `ValueObjectVisitorDispatcher` The new `ValueObjectVisitorDispatcher` receives the `ValueObjectVisitor`s tagged `app.rest.output.value_object.visitor`. As not all value FQCNs are handled, the new `ValueObjectVisitorDispatcher` also receives the default one as a fallback. ``` services: #… App\Rest\Output\ValueObjectVisitorDispatcher: class: App\Rest\Output\ValueObjectVisitorDispatcher arguments: - !tagged_iterator { tag: 'app.rest.output.value_object.visitor', index_by: 'type' } - '@Ibexa\Contracts\Rest\Output\ValueObjectVisitorDispatcher' ``` ``` visitors = []; foreach ($visitors as $type => $visitor) { $this->visitors[$type] = $visitor; } } public function setOutputVisitor(Visitor $outputVisitor): void { $this->outputVisitor = $outputVisitor; $this->valueObjectVisitorDispatcher->setOutputVisitor($outputVisitor); } public function setOutputGenerator(Generator $outputGenerator): void { $this->outputGenerator = $outputGenerator; $this->valueObjectVisitorDispatcher->setOutputGenerator($outputGenerator); } public function visit($data) { $className = $data::class; if (isset($this->visitors[$className])) { return $this->visitors[$className]->visit($this->outputVisitor, $this->outputGenerator, $data); } return $this->valueObjectVisitorDispatcher->visit($data); } } ``` ## New `Output\Visitor` service The following new pair of `Ouput\Visitor` entries associates `Accept` headers starting with `application/app.api.` to the new `ValueObjectVisitorDispatcher` for both XML and JSON. A priority is set higher than other `ibexa.rest.output.visitor` tagged built-in services. ``` parameters: #… app.rest.output.visitor.xml.regexps: ['(^application/app\.api\.[A-Za-z]+\+xml$)'] app.rest.output.visitor.json.regexps: ['(^application/app\.api\.[A-Za-z]+\+json$)'] services: #… app.rest.output.visitor.xml: class: Ibexa\Contracts\Rest\Output\Visitor arguments: - '@Ibexa\Rest\Output\Generator\Xml' - '@App\Rest\Output\ValueObjectVisitorDispatcher' tags: - { name: ibexa.rest.output.visitor, regexps: app.rest.output.visitor.xml.regexps, priority: 20 } app.rest.output.visitor.json: class: Ibexa\Contracts\Rest\Output\Visitor arguments: - '@Ibexa\Rest\Output\Generator\Json' - '@App\Rest\Output\ValueObjectVisitorDispatcher' tags: - { name: ibexa.rest.output.visitor, regexps: app.rest.output.visitor.json.regexps, priority: 20 } ``` ## Testing the new media-type In the following example, `curl` and `diff` commands are used to compare the default media type (`application/vnd.ibexa.api.Location+xml`) with the new `application/app.api.Location+xml`. ``` diff --ignore-space-change \ <(curl --silent https://api.example.com/api/ibexa/v2/content/locations/1/2) \ <(curl --silent https://api.example.com/api/ibexa/v2/content/locations/1/2 --header 'Accept: application/app.api.Location+xml'); ``` ``` 2c2,3 < --- > > 37a39,42 > > > > ``` # Creating new REST resource To create a new REST resource, you need to prepare: - the REST route leading to a controller action - the controller and its action - one or several `InputParser` objects if the controller needs to receive a payload to treat, one or several value classes to represent this payload and potentially one or several new media types to type this payload in the `Content-Type` header (optional) - one or several new value classes to represent the controller action result, their `ValueObjectVisitor` to help the generator to turn this into XML or JSON and potentially one or several new media types to claim in the `Accept` header the desired value (optional) - the addition of this resource route to the REST root (optional) In the following example, you add a greeting resource to the REST API. It's available through `GET` and `POST` methods. `GET` sets default values while `POST` allows inputting custom values. ## Route New REST routes should use the [REST URI prefix](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_usage/rest_api_usage/#uri-prefix) for consistency. To ensure that they do, in the `config/routes.yaml` file, while importing a REST routing file, use `ibexa.rest.path_prefix` parameter as a `prefix`. ``` app.rest: resource: routes_rest.yaml prefix: '%ibexa.rest.path_prefix%' ``` The `config/routes_rest.yaml` file imported above is created with the following configuration: ``` app.rest.greeting: path: '/greet' controller: App\Rest\Controller\DefaultController::helloWorld methods: [GET] ``` ### CSRF protection If a REST route is designed to be used with [unsafe methods](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_usage/rest_requests/#request-method), the CSRF protection is enabled by default like for built-in routes. You can disable it by using the route parameter `csrf_protection`. ``` app.rest.greeting: path: '/greet' controller: App\Rest\Controller\DefaultController::helloWorld methods: [GET,POST] defaults: csrf_protection: false ``` ## Controller ### Controller service You can use the following configuration to have all controllers from the `App\Rest\Controller\` namespace (files in the `src/Rest/Controller/` folder) to be set as REST controller services. ``` services: #… App\Rest\Controller\: resource: '../src/Rest/Controller/' parent: Ibexa\Rest\Server\Controller autowire: true autoconfigure: true tags: [ 'controller.service_arguments' ] ``` Having the REST controllers set as services enables using features such as the `InputDispatcher` service in the [Controller action](#controller-action). ### Controller action A REST controller should: - return a value object and have a `Generator` and `ValueObjectVisitor`s producing the XML or JSON output - extend `Ibexa\Rest\Server\Controller` to inherit utils methods and properties like `InputDispatcher` or `RequestParser` ``` getMethod()) { return $this->inputDispatcher->parse( new Message( ['Content-Type' => $request->headers->get('Content-Type')], $request->getContent() ) ); } return new Greeting(); } } ``` If the returned value was depending on a location, it could have been wrapped in a `CachedValue` to be cached by the reverse proxy (like Varnish) for future calls. `CachedValue` is used in the following way: ``` return new CachedValue( new MyValue($args…), ['locationId'=> $locationId] ); ``` ## Value and ValueObjectVisitor ``` setHeader('Content-Type', $generator->getMediaType('Greeting')); $generator->startObjectElement('Greeting'); $generator->attribute('href', $this->router->generate('app.rest.greeting')); $generator->valueElement('Salutation', $data->salutation); $generator->valueElement('Recipient', $data->recipient); $generator->valueElement('Sentence', "{$data->salutation} {$data->recipient}"); $generator->endObjectElement('Greeting'); } } ``` The `Values/Greeting` class is linked to its `ValueObjectVisitor` through the service tag. ``` services: #… App\Rest\ValueObjectVisitor\Greeting: parent: Ibexa\Contracts\Rest\Output\ValueObjectVisitor tags: - { name: ibexa.rest.output.value_object.visitor, type: App\Rest\Values\Greeting } ``` Here, the media type is `application/vnd.ibexa.api.Greeting` plus a format. To have a different vendor than the default, you could create a new `Output\Generator` or hard-code it in the `ValueObjectVisitor` like in the [`RestLocation` example](https://doc.ibexa.co/en/latest/api/rest_api/extending_rest_api/adding_custom_media_type/#new-restlocation-valueobjectvisitor). ## InputParser A REST resource could use route parameters to handle input, but this example illustrates the usage of an input parser. For this example, the structure is a `GreetingInput` root node with two leaf nodes, `Salutation` and `Recipient`. ``` Good morning'; curl https://api.example.com/api/ibexa/v2/greet --include --request POST \ --header 'Content-Type: application/vnd.ibexa.api.GreetingInput+json' \ --data '{"GreetingInput": {"Salutation": "Good day", "Recipient": "Earth"}}' \ --header 'Accept: application/vnd.ibexa.api.Greeting+json'; ``` ``` HTTP/1.1 200 OK Content-Type: application/vnd.ibexa.api.greeting+xml Hello World Hello World HTTP/1.1 200 OK Content-Type: application/vnd.ibexa.api.greeting+xml Good morning World Good morning World HTTP/1.1 200 OK Content-Type: application/vnd.ibexa.api.greeting+json { "Greeting": { "_media-type": "application\/vnd.ibexa.api.Greeting+json", "_href": "\/api\/ibexa\/v2\/greet", "Salutation": "Good day", "Recipient": "Earth", "Sentence": "Good day Earth" } } ``` ## Registering resources in REST root You can add the new resource to the [root resource](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_usage/rest_api_usage/#rest-root) through a configuration with the following pattern: ``` ibexa_rest: system: : rest_root_resources: : mediaType: href: 'router.generate("", {routeParameter: value})' ``` The `router.generate` renders a URI based on the name of the route and its parameters. The parameter values can be a real value or a placeholder. For example, `'router.generate("ibexa.rest.load_location", {locationPath: "1/2"})'` results in `/api/ibexa/v2/content/locations/1/2` while `'router.generate("ibexa.rest.load_location", {locationPath: "{locationPath}"})'` gives `/api/ibexa/v2/content/locations/{locationPath}`. This syntax is based on Symfony's [expression language](https://symfony.com/doc/7.4/components/expression_language/index.html), an extensible component that allows limited/readable scripting to be used outside the code context. In this example, `app.rest.greeting` is available in every SiteAccess (`default`): ``` ibexa_rest: system: default: rest_root_resources: greeting: mediaType: Greeting href: 'router.generate("app.rest.greeting")' ``` You can place this configuration in any regular config file, like the existing `config/packages/ibexa.yaml`, or a new `config/packages/ibexa_rest.yaml` file. The above example adds the following entry to the root XML output: ``` ``` # REST API authentication This page refers to [REST API reference](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html), where you can find detailed information about REST API resources and endpoints. Five authentication methods are currently supported: session (default), JWT, basic, OAuth, and client certificate (SSL). You can only use one of those methods at the same time. Using HTTPS for authenticated traffic is highly recommended. For other security related subjects, see: - [Cross-origin requests](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_usage/rest_responses/#cross-origin) - [`access_control`](https://symfony.com/doc/7.4/security/access_control.html) > **Caution: SiteAccess login** > > The anonymous user is used to perform authentification requests. Therefore, the "Anonymous" role must have `user/login` permission on the SiteAccess that matches the REST domain or is passed through the [`X-Siteaccess` header](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_usage/rest_requests/#siteaccess). ## Session-based authentication This authentication method requires a session cookie to be sent with each request. If you use this authentication method with a web browser, this session cookie is automatically available as soon as your visitor logs in. Add it as a cookie to your REST requests to authenticate the user. Sessions are created to re-authenticate the user only (and perform authorization), not to hold session state in the service. Because of that, you can use this method as supporting AJAX-based applications even if it violates the principles of RESTful services. ### Configuration Session is the default method and is already enabled, so no configuration required. Enabling any other method disables session. ### Usage examples You can create a session for a visitor even if they're not logged in by sending the **`POST`** request to `/user/sessions`. To log out, use the **`DELETE`** request on the same resource. #### Establishing session ##### Creating session To create a session, execute the following REST request: **XML** ``` POST /user/sessions HTTP/1.1 Host: www.example.net Accept: application/vnd.ibexa.api.Session+xml Content-Type: application/vnd.ibexa.api.SessionInput+xml ``` ``` admin publish ``` ``` HTTP/1.1 201 Created Location: /user/sessions/go327ij2cirpo59pb6rrv2a4el2 Set-Cookie: IBX_SESSION_ID98defd6ee70dfb1dea416=go327ij2cirpo59pb6rrv2a4el2; domain=.example.net; path=/; expires=Wed, 13-Jan-2021 22:23:01 GMT; HttpOnly Content-Type: application/vnd.ibexa.api.Session+xml ``` ``` IBX_SESSION_ID98defd6ee70dfb1dea416 go327ij2cirpo59pb6rrv2a4el2 23lk.neri34ijajedfw39orj-3j93 ``` **JSON** ``` POST /user/sessions HTTP/1.1 Host: www.example.net Accept: application/vnd.ibexa.api.Session+json Content-Type: application/vnd.ibexa.api.SessionInput+json ``` ``` { "SessionInput": { "login": "admin", "password": "publish" } } ``` ``` HTTP/1.1 201 Created Location: /user/sessions/go327ij2cirpo59pb6rrv2a4el2 Set-Cookie: IBX_SESSION_ID98defd6ee70dfb1dea416=go327ij2cirpo59pb6rrv2a4el2; domain=.example.net; path=/; expires=Wed, 13-Jan-2021 22:23:01 GMT; HttpOnly Content-Type: application/vnd.ibexa.api.Session+xml ``` ``` { "Session": { "_media-type": "application\/vnd.ibexa.api.Session+json", "_href": "\/api\/ibexa\/v2\/user\/sessions\/jg1nhinvepsb9ivd10hbjbdp4l", "name": "IBX_SESSION_ID98defd6ee70dfb1dea416", "identifier": "go327ij2cirpo59pb6rrv2a4el2", "csrfToken": "23lk.neri34ijajedfw39orj-3j93", "User": { "_media-type": "application\/vnd.ibexa.api.User+json", "_href": "\/api\/ibexa\/v2\/user\/users\/14" } } } ``` ##### Logging in with active session Logging in is similar to session creation, with one important detail: the CSRF token obtained in the previous step is added to the new request through the `X-CSRF-Token` header. **XML** ``` POST /user/sessions HTTP/1.1 Host: www.example.net Accept: application/vnd.ibexa.api.Session+xml Content-Type: application/vnd.ibexa.api.SessionInput+xml Cookie: IBX_SESSION_ID98defd6ee70dfb1dea416=go327ij2cirpo59pb6rrv2a4el2 X-CSRF-Token: 23lk.neri34ijajedfw39orj-3j93 ``` ``` admin publish ``` ``` HTTP/1.1 200 OK Content-Type: application/vnd.ibexa.api.Session+xml ``` ``` IBX_SESSION_ID98defd6ee70dfb1dea416 go327ij2cirpo59pb6rrv2a4el2 23lk.neri34ijajedfw39orj-3j93 ``` **JSON** ``` POST /user/sessions HTTP/1.1 Host: www.example.net Accept: application/vnd.ibexa.api.Session+json Content-Type: application/vnd.ibexa.api.SessionInput+json Cookie: IBX_SESSION_ID98defd6ee70dfb1dea416=go327ij2cirpo59pb6rrv2a4el2 X-CSRF-Token: 23lk.neri34ijajedfw39orj-3j93 ``` ``` { "SessionInput": { "login": "admin", "password": "publish" } } ``` ``` HTTP/1.1 200 OK Content-Type: application/vnd.ibexa.api.Session+json ``` ``` { "Session": { "_media-type": "application\/vnd.ibexa.api.Session+json", "_href": "\/api\/ibexa\/v2\/user\/sessions\/jg1nhinvepsb9ivd10hbjbdp4l", "name": "IBX_SESSION_ID98defd6ee70dfb1dea416", "identifier": "go327ij2cirpo59pb6rrv2a4el2", "csrfToken": "23lk.neri34ijajedfw39orj-3j93", "User": { "_media-type": "application\/vnd.ibexa.api.User+json", "_href": "\/api\/ibexa\/v2\/user\/users\/14" } } } ``` #### Using session ##### Session cookie You can now add the previously set cookie to requests to be executed with the logged-in user. ``` GET /content/locations/1/5 HTTP/1.1 Host: www.example.net Accept: Accept: application/vnd.ibexa.api.Location+xml Cookie: IBX_SESSION_ID98defd6ee70dfb1dea416=go327ij2cirpo59pb6rrv2a4el2 ``` ##### CSRF token It can be important to keep the CSRF token (`csrfToken`) for the duration of the session, because you must send this token in every request that uses [unsafe HTTP methods](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_usage/rest_requests/#request-method) (others than the safe GET or HEAD or OPTIONS) when a session has been established. It should be sent with an `X-CSRF-Token` header. Only three built-in routes can accept unsafe methods without CSRF, the sessions routes starting with `/user/sessions` to create, refresh or delete a session. ``` DELETE /content/types/32 HTTP/1.1 Host: www.example.net Cookie: IBX_SESSION_ID98defd6ee70dfb1dea416=go327ij2cirpo59pb6rrv2a4el2 X-CSRF-Token: 23lk.neri34ijajedfw39orj-3j93 ``` If an unsafe request is missing the CSRF token, or the token has incorrect value, an error is returned: `401 Unauthorized`. ##### Rich client application security concerns The purpose of CSRF protection is to prevent users from accidentally running harmful operations by being tricked into executing an HTTP(S) request against a web applications they're logged into. In browsers this action is blocked by lack of CSRF token. However, if you develop a rich client application (for example, JavaScript, JAVA, iOS, or Android), that is: - Registering itself as a protocol handler: - Exposes unsafe methods in any way - Authenticates using either: - Session-based authentication - "Client side session" by remembering user login/password Then, you have to make sure to confirm with the user if they want to perform an unsafe operation. Example: A rich JavaScript/web application uses `navigator.registerProtocolHandler()` to register "web+ez:" links to go against REST API. It uses a session-based authentication, and it's in widespread use across the net, or/and it's used by everyone within a company. A person with minimal insight into this application and the company can easily send out the following link to all employees in that company in email: `latest reports`. #### Logging out from session To log out is to `DELETE` the session using its ID (like in the cookie). As this is an unsafe method, the CSRF token must be presented. ``` DELETE /user/sessions/go327ij2cirpo59pb6rrv2a4el2 HTTP/1.1 Host: www.example.net Cookie: IBX_SESSION_ID98defd6ee70dfb1dea416=go327ij2cirpo59pb6rrv2a4el2 X-CSRF-Token: 23lk.neri34ijajedfw39orj-3j93 ``` ## JWT authentication ### Configuration See [JWT authentication](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/security/development_security/#jwt-authentication) for configuration instructions. ### Usage example After you configure JWT authentication for REST, you can get the JWT token through the following request: **JSON** ``` POST /user/token/jwt HTTP/1.1 Host: Accept: application/vnd.ibexa.api.JWT+json Content-Type: application/vnd.ibexa.api.JWTInput+json ``` Provide the username and password in the request body: ``` { "JWTInput": { "username": "admin", "password": "publish" } } ``` If credentials are valid, the server response contains a token: ``` { "JWT": { "_media-type": "application/vnd.ibexa.api.JWT+xml", "_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9…-QBE4-6eKNjg" } } ``` You can then use this token in your request instead of username and password. ``` GET /content/locations/1/5/children Host: Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9…-QBE4-6eKNjg Accept: application/vnd.ibexa.api.LocationList+json ``` ## HTTP basic authentication For more information, see [HTTP Authentication: Basic and Digest Access Authentication](https://datatracker.ietf.org/doc/html/rfc2617). ### Configuration If the installation has a dedicated host for REST, you can enable HTTP basic authentication only on this host by setting a firewall like in the following example before the `ibexa_front` one: ``` ibexa_rest: host: ^api\.example\.com$ http_basic: realm: Ibexa DXP REST API ``` > **Caution: Back office uses REST API** > > Back office uses the REST API too (for some parts like the Location tree or the Calendar) on its own domain. > > - If the back office SiteAccess matches `//admin.example.com` (through `Map\Host`, `HostElement` or `HostText`), it calls the REST API under `//admin.example.com/api/ibexa/v2`; > - If the back office SiteAccess matches `//localhost/admin` (through `URIElement`, `Map\URI` or `Regex\URI`), it calls the REST API under `//localhost/api/ibexa/v2` because SiteAccess matching with REST isn't enabled at URL level. > > If you enable basic authentication for `pattern: ^/api/ibexa/v2` to use it in your front office across both production and development environments, your development environment's back office cannot work correctly. This back office tries to access REST through the same URL as the front office. Even when logged in back office and using the [X-SiteAccess header](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_usage/rest_requests/#siteaccess), the firewall blocks access to REST as you're not logged through basic authentification. Therefore, some back office features don't work. > > If basic authentication is used only for REST API, it's better to have a dedicated domain even on a development environment. For example, map an `api.localhost` in your `hosts` file and set the firewall for `host: ^api\.(example\.com|localhost)$`. ### Usage example Basic authentication requires the username and password to be sent *(username:password)*, base64 encoded, with each request. For details, see [RFC 2617](https://datatracker.ietf.org/doc/html/rfc2617). Most HTTP client libraries and REST libraries support this method. [Creating content with binary attachments](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_usage/rest_requests/#creating-content-with-binary-attachments) is an example of using basic authentication with [cURL](https://www.php.net/manual/en/book.curl.php) and its `CURLOPT_USERPWD`. **Raw HTTP request with basic authentication** ``` GET / HTTP/1.1 Host: api.example.com Accept: application/vnd.ibexa.api.Root+json Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== ``` ## OAuth For more information, see [OAuth 2.0 protocol for authorization](https://oauth.net/2/). ## SSL client authentication The REST API provides authentication of a user by a subject in a client certificate delivered by the web server configured as SSL endpoint. # GraphQL [GraphQL](https://graphql.org/) is a query language for the API. The GraphQL implementation for Ibexa DXP is located in [`ibexa/graphql`](https://github.com/ibexa/graphql). ## Setup Using GraphQL requires a domain schema. Before using GraphQL for the first time, or anytime you modify content types or product types in your installation, you need to generate the schema: ``` php bin/console ibexa:graphql:generate-schema php bin/console cache:clear ``` YAML files with the schema are located in `config/graphql/types/ibexa`. They contain information about the domain objects and the fields you can [query](https://doc.ibexa.co/en/latest/api/graphql/graphql_queries/index.md) and [operate on](https://doc.ibexa.co/en/latest/api/graphql/graphql_operations/index.md). ### Schema generation limitations GraphQL schema cannot be generated for names that don't follow the [GraphQL specification](http://spec.graphql.org/June2018/#sec-Names), for example names that start with a digit. This concerns image variations, content types, content type groups, product types, and field definition identifiers. It's recommended to rename the relevant identifiers. Failure to generate schema is registered in logs. To find identifiers that aren't included in the schema, look for "Skipped schema generation" log messages, for example: `Skipped schema generation for Image Variation`. ## Domain schema GraphQL for Ibexa DXP is based on the content types (including product types), content type groups, and content items defined in the repository. For each content type the schema exposes a singular and plural field, for example, `article` and `articles`. Use the singular field to query a single content item, and the plural to get a whole `Connection` (a list of content items that supports pagination). With the queries you can inspect: - the existing types - details of content types, and their fields in the context of developing your own application You can request additional content information such as the section or Objects states, available under the `_info` field. You can also query content type and content type group information through the `_info` and `_types` fields. ### Repository schema The repository schema, accessed through `_repository`, exposes the Ibexa DXP repository in a manner similar to the [Public PHP API](https://doc.ibexa.co/en/latest/api/php_api/php_api/index.md). The `_repository` field also enables you to query, for example, object states configured for the repository. ### Custom schemas You can also use your own [custom schema](https://doc.ibexa.co/en/latest/api/graphql/graphql_customization/#custom-schema). ### SiteAccesses and multiple Repositories GraphQL is SiteAccess-aware, but can have only one schema per installation. This means you cannot use GraphQL with multiple repositories. When you request a URL from a SiteAccess that is different than the current one, the API generates it for the content item's SiteAccess, with an absolute URL if necessary. ## Authentication GraphQL for Ibexa DXP supports session-based authentication. You can get your session cookie by logging in through the interface or through a REST request. ### JWT authentication If you have [JWT authentication](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/security/development_security/#jwt-authentication) enabled, you can use the following query to get your authentication token: ``` mutation CreateToken { createToken(username: "admin", password: "publish") { token message } } ``` Response: ``` { "data": { "createToken": { "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2MDI4MzU5MTksImV4cCI6MTYwMjgzOTUxOSwicm9sZXMiOlsiUk9MRV9VU0VSIl0sInVzZXJuYW1lIjoiYWRtaW4ifQ.QtDjPU6q68fdvgm6O_1-aEoe-s7s-VQr-9CTMC9ba6E", "message": null } } } ``` ## Usage You can access GraphQL with `/graphql`. ### GraphiQL client The [GraphiQL interactive client](https://github.com/graphql/graphiql) is included in the installation. Access it through `/graphiql`. Here you can run your queries and preview the results in a readable format. ### Reference GraphiQL offers side-by-side reference based on your generated schema in the **Docs** pane. # GraphQL queries ## Querying content You can query a single content item or a list of content items using fields defined in the domain schema. ### Get a content item To get a specific content item by its content ID, location ID, or URL alias, use its relevant singular field, for example `article`, `folder`, or `image`. ``` { content { article(contentId: 62) { title author { name } } } } ``` Response: ``` { "data": { "content": { "article": { "title": "Travel literature, how to get started", "author": [ { "name": "Administrator User" } ] } } } } ``` You can request any fields of the content item. In the example above, these are `title` and `author`. You can also query the generic `item` object. The `item` object references a content item, but you can also get its [location information](#querying-locations). The query accepts `locationId`, `remoteId`, and `urlAlias` as arguments. ``` { item(locationId: 2) { _name ... on FolderItem { name } ... on LandingPageItem { name } ... on ArticleItem { title } } } ``` Response: ``` { "data": { "item": { "_name": "Ibexa Digital Experience Platform" } } } ``` #### Get language versions To get fields of a content item in a specific language, use the `language` argument. The language must be configured for the current SiteAccess. ``` { content { article(id: 57) { title: title(language: eng_GB) title_PL: title(language: pol_PL) } } } ``` Response: ``` { "data": { "content": { "article": { "title": "Most interesting cat breeds", "title_PL": "Najciekawsze rasy kotów" } } } } ``` When you don't specify a language, the response contains the most prioritized translation. ### Get a group of content items To get a list of all content items of a selected type, use the plural field, for example, `articles`: ``` { content { articles { edges { node { _location { id } title author { name } } } } } } ``` Response: ``` { "data": { "content": { "articles": { "edges": [ { "node": { "_location": { "id": 57 }, "title": "Travel literature, How to get started", "author": [ { "name": "Administrator User" } ] } }, { "node": { "_location": { "id": 58 }, "title": "Why we love NYC", "author": [ { "name": "Administrator User" } ] } }, # ... ] } } } } ``` > **Tip: Edges** > > `edges` are used when querying plural fields to offer [pagination](#pagination). ### Get content type information To get the IDs and names of all Fields in the `article` content type: ``` { content { _types { article { _info { fieldDefinitions { id name } } } } } } ``` Response: ``` { "data": { "content": { "_types": { "article": { "_info": { "fieldDefinitions": [ { "id": 1, "name": "Title" }, { "id": 152, "name": "Short title" }, { "id": 153, "name": "Author" }, { "id": 120, "name": "Intro" }, { "id": 121, "name": "Body" }, { "id": 123, "name": "Enable comments" }, { "id": 154, "name": "Image" } ] } } } } } } ``` ## Querying Locations You can get the Location object from any item by querying for `_location` or `_allLocations`. When you use `_location`, the API returns: - the location specified in the `locationId` or `urlAlias` argument - the location based on the current SiteAccess - the main location ``` { content { folder (contentId: 133) { _allLocations { pathString } } } } ``` Response: ``` { "data": { "content": { "folder": { "_allLocations": [ { "pathString": "/1/2/128/132/" }, { "pathString": "/1/2/133/" } ] } } } } ``` To query the URL alias of a content item, use `_url`. This returns the "best" URL alias for this content item based on its main Location and the current SiteAccess: ``` { content { folder (contentId: 1) { _url } } } ``` Response: ``` { "data": { "content": { "folder": { "_url": "/site/ez-platform" } } } } ``` ## Getting children of a Location To get a [location's](#querying-locations) children, it's recommended to use the [Query field](https://doc.ibexa.co/en/latest/templating/queries_and_controllers/content_queries/#content-query-field). Alternatively, you can query the `children` property of an `item` or `content` object: ``` { item(locationId: 2) { _location { children { edges { node { content { _name _type { name } } } } } } } } ``` Response: ``` { "data": { "item": { "_location": { "children": { "edges": [ { "node": { "content": { "_name": "Ibexa Platform", "_type": { "name": "Folder" } } } }, { "node": { "content": { "_name": "Product Catalog", "_type": { "name": "Product catalog" } } } } ] } } } } ``` ## Querying products You can query a single product, products of one type, or all products by providing criteria. > **Note: Note** > > GraphQL schema for product catalog is generated only when at least one product type exists in the system. If your queries fail, make sure you regenerated the schema. To get a single product by its code: ``` { products { single(code: "DRESUN") { name productType {name} createdAt { timestamp } } } } ``` Response: ``` { "data": { "products": { "single": { "name": "Sundress", "productType": { "name": "Dress" }, "createdAt": { "timestamp": 1649229733 } } } } } ``` To get products of a specific type: ``` { products { byType { dresses { edges { node { name code } } } } } } ``` Response: ``` { "data": { "products": { "byType": { "dresses": { "edges": [ { "node": { "name": "Sundress", "code": "DRESUN" } }, { "node": { "name": "Cocktail dress", "code": "DRECO" } } ] } }, } } } ``` To get all products, using specific criteria (in this case, unavailable products): ``` { products { all( availability:unavailable sortBy: [name] ) { edges { node { name code } } } } } ``` Response: ``` { "data": { "products": { "all": { "edges": [ { "node": { "name": "Cocktail dress", "code": "DRECO" } }, { "node": { "name": "Sundress", "code": "DRESUN" } } ] } } } } ``` ## Filtering To get all articles with a specific text: ``` { content { articles(query: {Text:"travel"}) { edges { node { title } } } } } ``` Response: ``` { "data": { "content": { "articles": { "edges": [ { "node": { "title": "Travel literature, How to get started" } }, { "node": { "title": "Travel with your dog" } } ] } } } } ``` To filter products based on content fields: ``` { products { all { edges { node { fields { ... on DressContentFields { name description { plaintext } } _all { fieldDefIdentifier value } } } } } } } ``` ### Querying product attributes To filter products based on attributes: ``` { products { single(code: "BLUELACE") { attributes { ... on DressAttributes { measure { reason } lace_color { identifier value } } } } } } ``` If the attribute type (in this case, `measure`) cannot be found in the schema, the response is: ``` { "data": { "products": { "single": { "attributes": { "measure": { "reason": "This attribute type isn't yet part of the schema." }, "lace_color": { "identifier": "lace_color", "value": "#387be8" } } } } } } ``` You can also query attributes by providing the attribute type: ``` { products { all { edges { node { attributes { _all { ... on ColorAttribute { identifier name colorValue: value } ... on IntegerAttribute { identifier name sizeValue: value } } } } } } } } ``` > **Note: Note** > > You need to use aliases (for example, `sizeValue`) when querying attributes by the attribute type due to the conflicting return types. Response: ``` { "data": { "products": { "all": { "edges": [ { "node": { "attributes": { "_all": [ { "identifier": "size", "name": "Size", "sizeValue": 36 }, { "identifier": "color", "name": "Color", "colorValue": "#fcff38" } ] } } }, { "node": { "attributes": { "_all": [ { "identifier": "color", "name": "Color", "colorValue": "#000000" }, { "identifier": "size", "name": "Size", "sizeValue": 40 } ] } } } ] } } } } ``` ## Sorting You can sort query results using `sortBy`: ``` { content { articles(sortBy: _datePublished) { edges { node { title } } } } } ``` You can use an array of clauses as well. To reverse the item list, add `_desc` after the clause: ``` articles(sortBy:[_datePublished,_desc]) ``` ## Pagination GraphQL offers [cursor-based pagination](https://graphql.org/learn/pagination/) for paginating query results. You can paginate plural fields by using `edges`: ``` { content { articles(sortBy: _datePublished, first:3) { pageInfo { hasNextPage endCursor } edges { node { title } } } } } ``` This query returns the first three articles, ordered by their publication date. If the current `Connection` (list of results) isn't finished yet and there are more items to read, `hasNextPage` is `true`. For the `children` node, you can use the following pagination method: ``` { _repository { location(locationId: 2) { children(first: 3) { pages { number cursor } edges { node { content { _name } } } } } } } ``` Response: ``` { "data": { "_repository": { "location": { "children": { "pages": [ { "number": 2, "cursor": "YXJyYXljb25uZWN0aW9uOjE=" }, { "number": 3, "cursor": "YXJyYXljb25uZWN0aW9uOjM=" } ], "edges": [ { # ... } ] } } } } } ``` In the response, `number` contains page numbers, starting with 2 (because 1 is the default). To request a specific page, provide the `cursor` as an argument to `children`: ``` children(first: 3, after: "YXJyYXljb25uZWN0aW9uOjM=") ``` ### Get Matrix field type To get a Matrix field type with GraphQL, see [Matrix field type reference](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/matrixfield/index.md). # GraphQL operations Operations on content in GraphQL are performed by using [mutations](https://graphql.org/learn/mutations/). They include creating, updating, and deleting content items. The schema contains two mutations per content type, for example, `createFolder`, and `updateFolder`. You can also make use of the generic `deleteContent` and `uploadFiles` mutations. ## Creating content Create a new Folder as a child of Location `2` with: ``` mutation createFolder { createFolder( language: eng_GB parentLocationId: 2 input: { name: "New Folder" } ) { id } } ``` Response: ``` { "data": { "createFolder": { "id": "RG9tYWluQ29udGVudDo2NA==" } } } ``` ## Updating content Modify the name of a Folder content item with: ``` mutation updateFolder { updateFolder( language: eng_GB contentId: 64 input: { name: "New Folder name" } ) { id } } ``` Response: ``` { "data": { "updateFolder": { "id": "RG9tYWluQ29udGVudDo2NA==" } } } ``` The input for updating a content item is the same as when creating it, but all fields are optional. ## Deleting content You can delete any content item by providing its `contentId` (or its GraphQL opaque ID under `id`): ``` mutation deleteBlogPost { deleteContent(contentId: 64) { id contentId } } ``` Response: ``` { "data": { "deleteContent": { "id": "Rm9sZGVyQ29udGVudDo2NA==", "contentId": 64 } } } ``` ## File upload > **Note: Note** > > Uploading binary files isn't possible through GraphiQL. You can use alternative third-party clients such as [Altair GraphQL](https://altairgraphql.dev/). Uploading files makes use of dedicated mutations per content type, for example: ``` mutation CreateImage($file: FileUpload!) { createImage( parentLocationId: 51, language: eng_GB, input: { name: "An image created over GraphQL", image: { alternativeText: "The alternative text", file: $file } } ) { _info { id mainLocationId } name image { fileName alternativeText uri } } } ``` The file is provided as the `$file` variable, defined as an `UploadFile`. You can include this mutation in a cURL request under `operations`: ``` curl -v -X POST \ /graphql \ -H "Cookie: $AUTH_COOKIE" \ -F 'operations={"query":"mutation createFile($file: FileUpload!) { ... }","variables":{"file": null}}' \ -F 'map={"image":["variables.file"]}' \ -F "image"=@/path/to/image.png ``` For example: ``` curl -v -X POST \ /graphql \ -H "Cookie: $AUTH_COOKIE" \ -F 'operations={"query":"mutation CreateImage($file: FileUpload!) { createImage( parentLocationId: 51, input: { name: \"An image created over GraphQL\", image: { alternativeText: \"The alternative text\", file: $file } }, language: \"eng-GB\" ) { _info { id mainLocationId } _url name image { fileName alternativeText uri } } }","variables":{"file": null}}' \ -F 'map={"image":["variables.file"]}' \ -F "image"=@/path/to/image.png ``` > **Note: Authentication** > > The example above requires you to set your authentication cookie in the `$AUTH_COOKIE` variable. For more information, see [Authentication](https://doc.ibexa.co/en/latest/api/graphql/graphql/#authentication). ### Uploading multiple files You can upload multiple files with one operation in a similar way by using the `uploadFiles` mutation. Here the files are provided in a `$files` variable and listed under `map` in the cURL request. ``` mutation UploadMultipleFiles($files: [FileUpload]!) { uploadFiles( locationId: 51, files: $files, language: eng_GB ) { files { _url _location { id } ... on ImageContent { name image { uri } } ... on FileContent { name file { uri } } ... on VideoContent { name file { uri } } } warnings } } ``` Include this mutation in a cURL request: ``` curl -v -X POST \ /graphql \ -H 'Cookie: $AUTH_COOKIE' \ -F 'operations={"query": "mutation UploadMultipleFiles($files: [FileUpload]!) { uploadFiles( locationId: 51, files: $files, languageCode: \"eng-GB\" ) { files { _url _location { id } ... on ImageContent { name image { uri } } ... on FileContent { name file { uri } } ... on VideoContent { name file { uri } } } warnings } }", "variables": {"files": [null, null, null, null, null]}}' \ -F 'map={"image1":["variables.files.0"], "image2":["variables.files.1"], "file1":["variables.files.2"], "file2":["variables.files.3"], "media":["variables.files.4"]}' \ -F "image1"=@/tmp/files/image1.png \ -F "image2"=@/tmp/files/image2.png \ -F "file1"=@/tmp/files/file1.pdf \ -F "file2"=@/tmp/files/file2.zip \ -F "media"=@/tmp/files/media.mp4 ``` # GraphQL customization ## Custom schema You can customize the GraphQL schema that is generated from your repository. You can use it if your application requires custom GraphQL resources, for instance for Doctrine entities. To do so, create a `config/graphql/types/Query.types.yaml` file. It is used as the GraphQL query root. In that file, add new fields that use any custom type or custom logic you require, based on [overblog/GraphQLBundle](https://github.com/overblog/GraphQLBundle). The custom schema should be created only after generating other schemas to avoid problems, especially if the custom schema depends on other schema elements. For example, `Type "Domain" inherited by "Query" not found.`. To avoid this problem during deployment, add the generated schemas to the repository. Update the schema in the event of any changes related to GraphQL and when changing the environment, for example from `dev` to `prod`. ### Configuration You can include the Ibexa DXP schema in two ways: either through inheritance or composition. #### Inheritance To use inheritance, apply the following configuration in `config/graphql/types/Query.types.yaml`: ``` Query: type: object inherits: - Domain config: fields: customField: type: object ``` #### Composition To use composition, define Ibexa DXP schema as a field in your custom schema. For example, in `config/graphql/types/Query.types.yaml`: ``` Query: type: object config: fields: myCustomField: {} myOtherCustomField: {} ibexa: type: Domain ``` ### Custom mutations Custom mutations are created in the same way as custom query configuration. A `config/graphql/types/Mutation.types.yaml` file is used as the source for mutation definitions in your schema. ``` Mutation: type: object inherits: [PlatformMutation] config: fields: createSomething: builder: Mutation builderConfig: inputType: CreateSomethingInput payloadType: SomethingPayload mutateAndGetPayload: '@=mutation('CreateSomething', [value])' CreateSomethingInput: type: relay-mutation-input config: fields: name: type: String SomethingPayload: type: object config: fields: name: type: String ``` ## Custom field name You can customize the name used by GraphQL as the content field name. Use this setting to avoid conflicts with field names that derive from a content type definition. ``` parameters: ibexa_graphql.schema.content.field_name.override: id: id_ ``` # Add GraphQL support to custom field types If you want to use custom field types in GraphQL, you need to map them. Their values and field definition structure, need to be defined, to interact with them using GraphQL. For example: | Name | Possible field value | Resolver | Field definition | | ------------- | -------------------------------- | ---------- | ---------------------------- | | Text Line | string | default | `TextLineFieldDefinition` | | Relation List | `Item` `ArticleItem` `ImageItem` | customized | `RelationListFieldDefinitio` | ## Map a custom field type There are two ways of mapping a custom field type: - configuration - custom `FieldDefinitionMapper` You need to write a custom `FieldDefinitionMapper` if the field definition settings and constraints impact how it's mapped to GraphQL. For example, the selection field type has a "multiple" option. If set to false, it accepts and returns a single value, but if set to true, it accepts and returns an array of values. If your field definition doesn't require additional clarifications, you can map it with configuration. ### Map with configuration To map a custom field type with configuration use a compiler pass to modify a container parameter, `ibexa.graphql.schema.content.mapping.field_definition_type`. It's a hash that maps a field type identifier (`ibexa_string`) to the following entries: - `value_type` - the GraphQL type values of the custom field. It can be a native type (string, int), or a custom type. If none is specified, string is used. - `value_resolver` - how values of this field are resolved and passed to the defined value type. If not specified, it receives the `Field` object for the field type: `field`. - `definition_type` - the GraphQL type the field definitions is mapped to. If not specified, it uses `FieldDefinition`. Compiler pass example that should be placed in `src/DependencyInjection/Compiler`: ``` hasParameter('ibexa.graphql.schema.content.mapping.field_definition_type')) { return; } $mapping = $container->getParameter('ibexa.graphql.schema.content.mapping.field_definition_type'); $mapping['my_custom_fieldtype'] = [ 'value_type' => 'MyCustomFieldValue', 'definition_type' => 'MyCustomFieldDefinition', 'value_resolver' => 'field.someProperty', ]; } } ``` ### Map with a custom `FieldDefinitionMapper` The `FieldDefinitionMapper` API uses service decorators. To register your own mapper, make it decorate the `Ibexa\GraphQL\Schema\Domain\Content\Mapper\FieldDefinition\DecoratingFieldDefinitionMapper` service: ``` services: App\GraphQL\Schema\MyCustomFieldDefinitionMapper: decorates: Ibexa\GraphQL\Schema\Domain\Content\Mapper\FieldDefinition\DecoratingFieldDefinitionMapper arguments: $innerMapper: '@.inner' ``` The `$innerMapper` argument passes the decorated mapper to the constructor. You can use the `DecoratingFieldDefinitionMapper` from the `graphql` package. It requires that you implement the `getFieldTypeIdentifier` method to tell which field type is covered by the mapper. Add `MyCustomFieldDefinitionMapper.php` mapper to `src/GraphQL/Schema`: ``` canMap($fieldDefinition)) { return parent::mapToFieldValueType($fieldDefinition); } ``` It's required for every implemented method, so that other mappers are called for the other field types. The [`RelationFieldDefinitionMapper`](https://github.com/ibexa/graphql/blob/main/src/lib/Schema/Domain/Content/Mapper/FieldDefinition/RelationFieldDefinitionMapper.php) example: ``` class RelationFieldDefinitionMapper extends DecoratingFieldDefinitionMapper implements FieldDefinitionMapper { public function mapToFieldValueType(FieldDefinition $fieldDefinition): ?string { if (!$this->canMap($fieldDefinition)) { return parent::mapToFieldValueType($fieldDefinition); } $settings = $fieldDefinition->getFieldSettings(); if (count($settings['selectionContentTypes']) === 1) { $contentType = $this->contentTypeService->loadContentTypeByIdentifier($settings['selectionContentTypes'][0]); $type = $this->nameHelper->itemName($contentType); } else { $type = 'Item'; } if (this->isMultiple(fieldDefinition)) { type = "[type]"; } return $type; } public function mapToFieldValueResolver(FieldDefinition $fieldDefinition): ?string { if (!$this->canMap($fieldDefinition)) { return parent::mapToFieldValueResolver($fieldDefinition); } isMultiple = this->isMultiple($fieldDefinition) ? 'true' : 'false'; return sprintf('@=resolver("DomainRelationFieldValue", [field, %s])', $isMultiple); } private function isMultiple(FieldDefinition $fieldDefinition) { $constraints = $fieldDefinition->getValidatorConfiguration(); return isset(constraints['RelationListValueValidator']) && constraints'RelationListValueValidator' !== 1; } } ``` The value type depends on the field definition allowed content types setting: - for types that return content items if there are no restrictions, or several types are allowed, the value is an `Item` The cardinality (single or collection) depends on the selection limit setting: - if only one item is allowed, the value is unique: `ArticleItem`, `FolderItem` - if there are no limits, or the limit is larger than 1, the value is a collection: `"[ArticleItem]", "[FolderItem]"`. #### Field input mapping The `mapToFieldValueInputType` method is used to document what input type is expected by field types that require a more complex input value. For example, `ibexa_matrix` generates its own input types depending on the configured columns. Example of a `MyCustomFieldDefinitionMapper` mapper for a complex field type: ``` class MyFieldDefinitionMapper extends DecoratingFieldDefinitionMapper implements FieldDefinitionMapper { public function mapToFieldValueInputType(ContentType contentType, FieldDefinition fieldDefinition): ?string { if (!this->canMap(fieldDefinition)) { return parent::mapToFieldValueInputType($fieldDefinition); } return this->nameMyFieldType(fieldDefinition); } } ``` ## Resolver expressions The following variables are available in the resolver's expression: - `field` is the current field, as an extension of the API's field object that proxies properties requests to the field Value - `content` is the resolved content item's `Content` - `location` is the content item's resolved location. For more information, see [Querying Locations](https://doc.ibexa.co/en/latest/api/graphql/graphql_queries/#querying-locations) - `item` is the content together with its location `\Ibexa\GraphQL\Value\Item` `RelationFieldValueBuilder` or `SelectionFieldValueBuilder` can be used as examples. # Event reference Ibexa DXP dispatches events during different actions. You can subscribe to these events to extend the functionality of the system. In most cases, two events are dispatched for every action, one before the action is completed, and one after. For example, copying a content item is connected with two events: `BeforeCopyContentEvent` and `CopyContentEvent`. ``` ['onCopyContent', 0], ]; } public function onCopyContent(CopyContentEvent $event): void { // your implementation } } ``` - [AI Actions events](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/api/event_reference/ai_action_events/): Events that are triggered when working with AI actions. - [Cart events](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/api/event_reference/cart_events/): Events that are triggered when working with carts. - [Product catalog events](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/api/event_reference/product_catalog_events/): Events that are triggered when working with products, prices, currencies, and attribute rendering. - [Collaboration events](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/api/event_reference/collaboration_events/): Events that are triggered when working with collaborative editing feature. - [Content events](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/api/event_reference/content_events/): Events that are triggered when working with content. - [Content type events](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/api/event_reference/content_type_events/): Events that are triggered when working with content types. - [Discounts events](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/api/event_reference/discounts_events/): Events that are triggered when working with discounts. - [Integrated help events](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/api/event_reference/integrated_help_events/): Events that are triggered when working with integrated help features like product tours. - [Language events](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/api/event_reference/language_events/): Events that are triggered when working with languages. - [Location events](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/api/event_reference/location_events/): Events that are triggered when working with content Locations. - [Object state events](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/api/event_reference/object_state_events/): Events that are triggered when working with object states and object state groups. - [Order management events](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/api/event_reference/order_management_events/): Events that are triggered when working with orders. - [Other events](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/api/event_reference/other_events/): Events that are triggered when working with bookmarks, notifications, settings, forms and others. - [Page events](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/api/event_reference/page_events/): Events that are triggered when working with pages and page blocks. - [Payment events](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/api/event_reference/payment_events/): Events that are triggered when working with payments and payment methods. - [Role events](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/api/event_reference/role_events/): Events that are triggered when working with roles. - [Section events](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/api/event_reference/section_events/): Events that are triggered when working with sections. - [Segmentation events](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/api/event_reference/segmentation_events/): Events that are triggered when working with segments. - [Site events](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/api/event_reference/site_events/): Events that are triggered when working with sites. - [Taxonomy events](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/api/event_reference/taxonomy_events/): Events that are triggered when working with taxonomy. - [Trash events](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/api/event_reference/trash_events/): Events that are triggered when working with Trash. - [Twig Components events](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/api/event_reference/twig_component_events/): Events that are triggered when rendering Twig Components. - [URL events](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/api/event_reference/url_events/): Events that are triggered when working with URLs, URL aliases and URL wildcards. - [User events](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/api/event_reference/user_events/): Events that are triggered when working with users and user groups. # Content events | Event | Dispatched by | Properties | | ---------------------------------- | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | `BeforeCreateContentDraftEvent` | `ContentService::createContentDraft` | `ContentInfo $contentInfo`, `VersionInfo $versionInfo`, `User $creator`, \`Language | | `CreateContentDraftEvent` | `ContentService::createContentDraft` | `Content $contentDraft`, `ContentInfo $contentInfo`, `VersionInfo $versionInfo`, `User $creator`, \`Language | | `BeforeCreateContentEvent` | `ContentService::createContent` | `ContentCreateStruct $contentCreateStruct`, `array $locationCreateStructs`, \`Content | | `CreateContentEvent` | `ContentService::createContent` | `ContentCreateStruct $contentCreateStruct`, `array $locationCreateStructs`, `Content $content`, \`string[] | | `BeforeUpdateContentEvent` | `ContentService::updateContent` | `VersionInfo $versionInfo`, `ContentUpdateStruct $contentUpdateStruct`, \`Content | | `UpdateContentEvent` | `ContentService::updateContent` | `Content $content`, `VersionInfo $versionInfo`, `ContentUpdateStruct $contentUpdateStruct`, \`string[] | | `BeforeUpdateContentMetadataEvent` | `ContentService::updateContentMetadata` | `ContentInfo $contentInfo`, `ContentMetadataUpdateStruct $contentMetadataUpdateStruct`, \`Content | | `UpdateContentMetadataEvent` | `ContentService::updateContentMetadata` | `Content $content`, `ContentInfo $contentInfo`, `ContentMetadataUpdateStruct $contentMetadataUpdateStruct` | | `BeforeCopyContentEvent` | `ContentService::copyContent` | `ContentInfo $contentInfo`, `LocationCreateStruct $destinationLocationCreateStruct`, `VersionInfo $versionInfo`, \`Content | | `CopyContentEvent` | `ContentService::copyContent` | `Content $content`, `ContentInfo $contentInfo`, `LocationCreateStruct $destinationLocationCreateStruct`, `VersionInfo $versionInfo` | | `BeforePublishVersionEvent` | `ContentService::publishVersion` | `VersionInfo $versionInfo`, \`Content | | `PublishVersionEvent` | `ContentService::publishVersion` | `Content $content`, `VersionInfo $versionInfo`, `string[] $translations` | | `BeforeDeleteContentEvent` | `ContentService::deleteContent` | `ContentInfo $contentInfo`, \`array | | `DeleteContentEvent` | `ContentService::deleteContent` | `array $locations`, `ContentInfo $contentInfo` | | `BeforeDeleteVersionEvent` | `ContentService::deleteVersion` | `VersionInfo $versionInfo` | | `DeleteVersionEvent` | `ContentService::deleteVersion` | `VersionInfo $versionInfo` | ## Relations | Event | Dispatched by | Properties | | --------------------------- | -------------------------------- | ------------------------------------------------------------------------------------- | | `BeforeAddRelationEvent` | `ContentService::addRelation` | `VersionInfo $sourceVersion`, `ContentInfo $destinationContent`, \`Relation | | `AddRelationEvent` | `ContentService::addRelation` | `Relation $relation`, `VersionInfo $sourceVersion`, `ContentInfo $destinationContent` | | `BeforeDeleteRelationEvent` | `ContentService::deleteRelation` | `VersionInfo $sourceVersion`, `ContentInfo $destinationContent` | | `DeleteRelationEvent` | `ContentService::deleteRelation` | `VersionInfo $sourceVersion`, `ContentInfo $destinationContent` | ## Content translations | Event | Dispatched by | Properties | | ------------------------------ | ----------------------------------- | ------------------------------------------- | | `BeforeDeleteTranslationEvent` | `ContentService::deleteTranslation` | `ContentInfo $contentInfo`, `$languageCode` | | `DeleteTranslationEvent` | `ContentService::deleteTranslation` | `ContentInfo $contentInfo`, `$languageCode` | ## Hiding and revealing | Event | Dispatched by | Properties | | -------------------------- | ------------------------------- | -------------------------- | | `BeforeHideContentEvent` | `ContentService::hideContent` | `ContentInfo $contentInfo` | | `HideContentEvent` | `ContentService::hideContent` | `ContentInfo $contentInfo` | | `BeforeRevealContentEvent` | `ContentService::revealContent` | `ContentInfo $contentInfo` | | `RevealContentEvent` | `ContentService::revealContent` | `ContentInfo $contentInfo` | # Content type events | Event | Dispatched by | Properties | | ------------------------------------ | --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | | `BeforeCreateContentTypeDraftEvent` | `ContentTypeService::createContentTypeDraft` | `ContentType $contentType`, \`ContentTypeDraft | | `CreateContentTypeDraftEvent` | `ContentTypeService::createContentTypeDraft` | `ContentTypeDraft $contentTypeDraft`, `ContentType $contentType` | | `BeforeCreateContentTypeEvent` | `ContentTypeService::createContentType` | `ContentTypeCreateStruct $contentTypeCreateStruct`, `array $contentTypeGroups`, \`ContentTypeDraft | | `CreateContentTypeEvent` | `ContentTypeService::createContentType` | `ContentTypeDraft $contentTypeDraft`, `ContentTypeCreateStruct $contentTypeCreateStruct`, `array $contentTypeGroups` | | `BeforeUpdateContentTypeDraftEvent` | `ContentTypeService::updateContentTypeDraft` | `ContentTypeDraft $contentTypeDraft`, `ContentTypeUpdateStruct $contentTypeUpdateStruct` | | `UpdateContentTypeDraftEvent` | `ContentTypeService::updateContentTypeDraft` | `ContentTypeDraft $contentTypeDraft`, `ContentTypeUpdateStruct $contentTypeUpdateStruct` | | `BeforeCopyContentTypeEvent` | `ContentTypeService::copyContentType` | `ContentType $contentType`, `User $creator`, \`ContentType | | `CopyContentTypeEvent` | `ContentTypeService::copyContentType` | `ContentType $contentTypeCopy`, `ContentType $contentType`, `User $creator` | | `BeforePublishContentTypeDraftEvent` | `ContentTypeService::publishContentTypeDraft` | `ContentTypeDraft $contentTypeDraft` | | `PublishContentTypeDraftEvent` | `ContentTypeService::publishContentTypeDraft` | `ContentTypeDraft $contentTypeDraft` | | `BeforeDeleteContentTypeEvent` | `ContentTypeService::deleteContentType` | `ContentType $contentType` | | `DeleteContentTypeEvent` | `ContentTypeService::deleteContentType` | `ContentType $contentType` | ## Content type groups | Event | Dispatched by | Properties | | ----------------------------------- | -------------------------------------------- | -------------------------------------------------------------------------------------------------- | | `BeforeCreateContentTypeGroupEvent` | `ContentTypeService::createContentTypeGroup` | `ContentTypeCreateStruct $contentTypeCreateStruct`, `array $contentTypeGroups`, \`ContentTypeDraft | | `CreateContentTypeGroupEvent` | `ContentTypeService::createContentTypeGroup` | `ContentTypeGroup $contentTypeGroup`, `ContentTypeGroupCreateStruct $contentTypeGroupCreateStruct` | | `BeforeUpdateContentTypeGroupEvent` | `ContentTypeService::updateContentTypeGroup` | `ContentTypeGroup $contentTypeGroup`, `ContentTypeGroupUpdateStruct $contentTypeGroupUpdateStruct` | | `UpdateContentTypeGroupEvent` | `ContentTypeService::updateContentTypeGroup` | `ContentTypeGroup $contentTypeGroup`, `ContentTypeGroupUpdateStruct $contentTypeGroupUpdateStruct` | | `BeforeDeleteContentTypeGroupEvent` | `ContentTypeService::deleteContentTypeGroup` | `ContentTypeGroup $contentTypeGroup` | | `DeleteContentTypeGroupEvent` | `ContentTypeService::deleteContentTypeGroup` | `ContentTypeGroup $contentTypeGroup` | ## Content type translations | Event | Dispatched by | Properties | | ----------------------------------------- | -------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | | `BeforeRemoveContentTypeTranslationEvent` | `ContentTypeService::removeContentTypeTranslation` | `ContentTypeDraft $contentTypeDraft`, `string $languageCode`, \`ContentTypeDraft | | `RemoveContentTypeTranslationEvent` | `ContentTypeService::removeContentTypeTranslation` | `ContentTypeDraft $newContentTypeDraft`, `ContentTypeDraft $contentTypeDraft`, `string $languageCode` | ## Field definitions | Event | Dispatched by | Properties | | ---------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | `BeforeAddFieldDefinitionEvent` | `ContentTypeService::addFieldDefinition` | `ContentTypeDraft $contentTypeDraft`, `FieldDefinitionCreateStruct $fieldDefinitionCreateStruct` | | `AddFieldDefinitionEvent` | `ContentTypeService::addFieldDefinition` | `ContentTypeDraft $contentTypeDraft`, `FieldDefinitionCreateStruct $fieldDefinitionCreateStruct` | | `BeforeUpdateFieldDefinitionEvent` | `ContentTypeService::updateFieldDefinition` | `ContentTypeDraft $contentTypeDraft`, `FieldDefinition $fieldDefinition`, `FieldDefinitionUpdateStruct $fieldDefinitionUpdateStruct` | | `UpdateFieldDefinitionEvent` | `ContentTypeService::updateFieldDefinition` | `ContentTypeDraft $contentTypeDraft`, `FieldDefinition $fieldDefinition`, `FieldDefinitionUpdateStruct $fieldDefinitionUpdateStruct` | | `BeforeRemoveFieldDefinitionEvent` | `ContentTypeService::removeFieldDefinition` | `ContentTypeDraft $contentTypeDraft`, `FieldDefinition $fieldDefinition` | | `RemoveFieldDefinitionEvent` | `ContentTypeService::removeFieldDefinition` | `ContentTypeDraft $contentTypeDraft`, `FieldDefinition $fieldDefinition` | ## Assigning to groups | Event | Dispatched by | Properties | | ------------------------------------- | ---------------------------------------------- | ---------------------------------------------------------------- | | `BeforeAssignContentTypeGroupEvent` | `ContentTypeService::assignContentTypeGroup` | `ContentType $contentType`, `ContentTypeGroup $contentTypeGroup` | | `AssignContentTypeGroupEvent` | `ContentTypeService::assignContentTypeGroup` | `ContentType $contentType`, `ContentTypeGroup $contentTypeGroup` | | `BeforeUnassignContentTypeGroupEvent` | `ContentTypeService::unassignContentTypeGroup` | `ContentType $contentType`, `ContentTypeGroup $contentTypeGroup` | | `UnassignContentTypeGroupEvent` | `ContentTypeService::unassignContentTypeGroup` | `ContentType $contentType`, `ContentTypeGroup $contentTypeGroup` | # Location events | Event | Dispatched by | Properties | | --------------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------- | | `BeforeCreateLocationEvent` | `LocationService::createLocation` | `ContentInfo $contentInfo`, `LocationCreateStruct $locationCreateStruct`, \`Location | | `CreateLocationEvent` | `LocationService::createLocation` | `Location $location`, `ContentInfo $contentInfo`, `LocationCreateStruct $locationCreateStruct` | | `BeforeUpdateLocationEvent` | `LocationService::updateLocation` | `Location $location`, `LocationUpdateStruct $locationUpdateStruct`, \`Location | | `UpdateLocationEvent` | `LocationService::updateLocation` | `Location $updatedLocation`, `Location $location`, `LocationUpdateStruct $locationUpdateStruct` | | `BeforeDeleteLocationEvent` | `LocationService::deleteLocation` | `Location $location` | | `DeleteLocationEvent` | `LocationService::deleteLocation` | `Location $location` | ## Hiding and revealing | Event | Dispatched by | Properties | | --------------------------- | --------------------------------- | -------------------------------------------------- | | `BeforeHideLocationEvent` | `LocationService::hideLocation` | `Location $location`, \`Location | | `HideLocationEvent` | `LocationService::hideLocation` | `Location $hiddenLocation`, `Location $location` | | `BeforeUnhideLocationEvent` | `LocationService::unhideLocation` | `Location $location`, \`Location | | `UnhideLocationEvent` | `LocationService::unhideLocation` | `Location $revealedLocation`, `Location $location` | ## Subtree and Location management | Event | Dispatched by | Properties | | ------------------------- | ------------------------------- | --------------------------------------------------------------------------- | | `BeforeCopySubtreeEvent` | `LocationService::copySubtree` | `Location $subtree`, `Location $targetParentLocation`, \`Location | | `CopySubtreeEvent` | `LocationService::copySubtree` | `Location $location`, `Location $subtree`, `Location $targetParentLocation` | | `BeforeMoveSubtreeEvent` | `LocationService::moveSubtree` | `Location $location`, `Location $newParentLocation` | | `MoveSubtreeEvent` | `LocationService::moveSubtree` | `Location $location`, `Location $newParentLocation` | | `BeforeSwapLocationEvent` | `LocationService::swapLocation` | `Location $location1`, `Location $location2` | | `SwapLocationEvent` | `LocationService::swapLocation` | `Location $location1`, `Location $location2` | # Product catalog events ## Products | Event | Dispatched by | Properties | | -------------------------------------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | | `BeforeCreateAttributeDefinitionEvent` | `AttributeDefinitionService::createAttributeDefinition` | `AttributeDefinitionCreateStruct $createStruct` | | `BeforeCreateAttributeGroupEvent` | `AttributeGroupService::createAttributeGroup` | `AttributeGroupCreateStruct $createStruct` | | `BeforeCreateProductEvent` | `ProductService::createProduct` | `ProductCreateStruct $createStruct` | | `BeforeDeleteAttributeDefinitionEvent` | `AttributeDefinitionService::deleteAttributeDefinition` | `AttributeDefinitionInterface $attributeDefinition` | | `BeforeDeleteAttributeGroupEvent` | `AttributeGroupService::deleteAttributeGroup` | `AttributeGroupInterface $attributeGroup` | | `BeforeDeleteProductEvent` | `ProductService::deleteProduct` | `ProductInterface $product` | | `BeforeUpdateAttributeDefinitionEvent` | `AttributeDefinitionService::updateAttributeDefinition` | `AttributeDefinitionInterface $attributeDefinition`, `AttributeDefinitionUpdateStruct $updateStruct` | | `BeforeUpdateAttributeGroupEvent` | `AttributeGroupService::updateAttributeGroup` | `AttributeGroupInterface $attributeGroup`, `AttributeGroupUpdateStruct $updateStruct` | | `BeforeUpdateProductEvent` | `ProductService::updateProduct` | `ProductUpdateStruct $updateStruct` | | `CreateAttributeDefinitionEvent` | `AttributeDefinitionService::createAttributeDefinition` | `AttributeDefinitionCreateStruct $createStruct`, `AttributeDefinitionInterface $attributeDefinition` | | `CreateAttributeGroupEvent` | `AttributeGroupService::createAttributeGroup` | `AttributeGroupCreateStruct $createStruct`, `AttributeGroupInterface $attributeGroup` | | `CreateProductEvent` | `ProductService::createProduct` | `ProductCreateStruct $createStruct`, `ProductInterface $product` | | `DeleteAttributeDefinitionEvent` | `AttributeDefinitionService::deleteAttributeDefinition` | `AttributeDefinitionInterface $attributeDefinition` | | `DeleteAttributeGroupEvent` | `AttributeGroupService::deleteAttributeGroup` | `AttributeGroupInterface $attributeGroup` | | `DeleteProductEvent` | `ProductService::deleteProduct` | `ProductInterface $product` | | `UpdateAttributeDefinitionEvent` | `AttributeDefinitionService::updateAttributeDefinition` | `AttributeDefinitionInterface $attributeDefinition`, `AttributeDefinitionUpdateStruct $updateStruct` | | `UpdateAttributeGroupEvent` | `AttributeGroupService::updateAttributeGroup` | `AttributeGroupInterface $attributeGroup`, `AttributeGroupUpdateStruct $updateStruct` | | `UpdateProductEvent` | `ProductService::updateProduct` | `ProductInterface $product`, `ProductUpdateStruct $updateStruct` | ## Product variants | Event | Dispatched by | Properties | | ---------------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------- | | `BeforeCreateProductVariantsEvent` | `ProductService::createProductVariants` | `ProductInterface $product`, `iterable $createStructs` | | `BeforeUpdateProductVariantEvent` | `ProductService::updateProductVariant` | `ProductVariantInterface $productVariant`, `ProductVariantUpdateStruct $updateStruct` | | `CreateProductVariantsEvent` | `ProductService::createProductVariants` | `ProductInterface $product`, `iterable $createStructs` | | `UpdateProductVariantEvent` | `ProductService::updateProductVariant` | `ProductVariantInterface $productVariant`, `ProductVariantUpdateStruct $updateStruct` | ## Product availability | Event | Dispatched by | Properties | | ---------------------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------- | | `BeforeCreateProductAvailabilityEvent` | `ProductAvailabilityService::createProductAvailability` | `ProductAvailabilityCreateStruct $createStruct` | | `BeforeDecreaseProductAvailabilityEvent` | `ProductAvailabilityService::decreaseProductAvailability` | `ProductInterface $product`, `int $amount` | | `BeforeDeleteProductAvailabilityEvent` | `ProductAvailabilityService::deleteProductAvailability` | `ProductInterface $product` | | `BeforeIncreaseProductAvailabilityEvent` | `ProductAvailabilityService::increaseProductAvailability` | `ProductInterface $product`, `int $amount` | | `BeforeUpdateProductAvailabilityEvent` | `ProductAvailabilityService::updateProductAvailability` | `ProductAvailabilityUpdateStruct $updateStruct` | | `CreateProductAvailabilityEvent` | `ProductAvailabilityService::createProductAvailability` | `ProductAvailabilityCreateStruct $createStruct`, `AvailabilityInterface $productAvailability` | | `DecreaseProductAvailabilityEvent` | `ProductAvailabilityService::decreaseProductAvailability` | `AvailabilityInterface $productAvailability`, `ProductInterface $product`, `int $amount` | | `DeleteProductAvailabilityEvent` | `ProductAvailabilityService::deleteProductAvailability` | `ProductInterface $product` | | `IncreaseProductAvailabilityEvent` | `ProductAvailabilityService::increaseProductAvailability` | `AvailabilityInterface $productAvailability ProductInterface $product`, `int $amount` | | `UpdateProductAvailabilityEvent` | `ProductAvailabilityService::updateProductAvailability` | `AvailabilityInterface $productAvailability`, `ProductAvailabilityUpdateStruct $updateStruct` | ## Price | Event | Dispatched by | Properties | | ------------------------ | ----------------------------------------- | -------------------------------------------------------------------------- | | `BeforeCreatePriceEvent` | `ProductPriceService::createProductPrice` | `ProductPriceCreateStructInterface $createStruct` | | `BeforeDeletePriceEvent` | `ProductPriceService::deletePrice` | `ProductPriceDeleteStructInterface $deleteStruct` | | `BeforeUpdatePriceEvent` | `ProductPriceService::updateProductPrice` | `ProductPriceUpdateStructInterface $updateStruct` | | `CreatePriceEvent` | `ProductPriceService::createProductPrice` | `ProductPriceCreateStructInterface $createStruct`, `PriceInterface $price` | | `DeletePriceEvent` | `ProductPriceService::deletePrice` | `ProductPriceDeleteStructInterface $deleteStruct` | | `UpdatePriceEvent` | `ProductPriceService::updateProductPrice` | `PriceInterface $price`, `ProductPriceUpdateStructInterface $updateStruct` | ## Currency | Event | Dispatched by | Properties | | --------------------------- | --------------------------------- | ------------------------------------------------------------------- | | `BeforeCreateCurrencyEvent` | `CurrencyService::createCurrency` | `CurrencyCreateStruct $createStruct` | | `BeforeDeleteCurrencyEvent` | `CurrencyService::deleteCurrency` | `CurrencyInterface $currency` | | `BeforeUpdateCurrencyEvent` | `CurrencyService::updateCurrency` | `CurrencyInterface $currency`, `CurrencyUpdateStruct $updateStruct` | | `CreateCurrencyEvent` | `CurrencyService::createCurrency` | `CurrencyCreateStruct $createStruct`, `CurrencyInterface $currency` | | `DeleteCurrencyEvent` | `CurrencyService::deleteCurrency` | `CurrencyInterface $currency` | | `UpdateCurrencyEvent` | `CurrencyService::updateCurrency` | `CurrencyInterface $currency`, `CurrencyUpdateStruct $updateStruct` | ## Catalogs | Event | Dispatched by | Properties | | -------------------------- | ------------------------------- | ---------------------------------------------------------------- | | `BeforeCreateCatalogEvent` | `CatalogService::createCatalog` | `CatalogCreateStruct $createStruct` | | `BeforeDeleteCatalogEvent` | `CatalogService::deleteCatalog` | `CatalogInterface $catalog` | | `BeforeUpdateCatalogEvent` | `CatalogService::updateCatalog` | `CatalogUpdateStruct $updateStruct` | | `CreateCatalogEvent` | `CatalogService::createCatalog` | `CatalogCreateStruct $createStruct`, `CatalogInterface $catalog` | | `DeleteCatalogEvent` | `CatalogService::deleteCatalog` | `CatalogInterface $catalog` | | `UpdateCatalogEvent` | `CatalogService::updateCatalog` | `CatalogUpdateStruct $updateStruct` | ## Attribute rendering The following event is dispatched when the [`ibexa_format_product_attribute`](https://doc.ibexa.co/en/latest/templating/twig_function_reference/product_twig_functions/#ibexa_format_product_attribute) Twig filter renders an attribute value. | Event | Dispatched by | Properties | | ----------------------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `ProductAttributeRenderEvent` | `ibexa_format_product_attribute` Twig filter | `list $templates`[`AttributeInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-AttributeInterface.html)`array $parameters` | For a usage example, see [Customize product attribute templates](https://doc.ibexa.co/en/latest/product_catalog/customize_product_attribute_templates/index.md). # Cart events Editions: Commerce | Event | Dispatched by | Properties | | ------------------------------- | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | | `AddEntryEvent` | `CartService::addEntry` | `CartInterface $cart`, `EntryAddStruct $entryAddStruct`, `CartInterface $cartResult` | | `BeforeAddEntryEvent` | `CartService::addEntry` | `CartInterface $cart`, `EntryAddStruct $entryAddStruct`, `?CartInterface $cartResult = null` | | `BeforeCreateCartEvent` | `CartService::createCart` | `CartCreateStruct $cartCreateStruct`, `?CartInterface $cartResult = null` | | `BeforeDeleteCartEvent` | `CartService::deleteCart` | `CartInterface $cart` | | `BeforeEmptyCartEvent` | `CartService::emptyCart` | `CartInterface $cart` | | `BeforeMergeCartsEvent` | `CartService::mergeCarts` | `CartInterface $targetCart`, `array $cartsToMerge`, `bool $deleteMergedCarts` | | `BeforeRemoveEntryEvent` | `CartService::removeEntry` | `CartInterface $cart`, `EntryInterface $entry`, `?CartInterface $cartResult = null` | | `BeforeUpdateCartMetadataEvent` | `CartService::updateCartMetadata` | `CartInterface $cart`, `CartMetadataUpdateStruct $cartUpdateStruct`, `?CartInterface $cartResult = null` | | `BeforeUpdateEntryEvent` | `CartService::updateEntry` | `CartInterface $cart`, `EntryInterface $entry`, `EntryUpdateStruct $entryUpdateStruct`, `?CartInterface $cartResult = null` | | `CreateCartEvent` | `CartService::createCart` | `CartCreateStruct $cartCreateStruct`, `CartInterface $cartResult` | | `DeleteCartEvent` | `CartService::deleteCart` | `CartInterface $cart` | | `EmptyCartEvent` | `CartService::emptyCart` | `CartInterface $cart` | | `MergeCartsEvent` | `CartService::mergeCarts` | `CartInterface $cartResult` | | `RemoveEntryEvent` | `CartService::removeEntry` | `CartInterface $cart`, `EntryInterface $entry`, `CartInterface $cartResult` | | `UpdateCartMetadataEvent` | `CartService::updateCartMetadata` | `CartInterface $cart`, `CartMetadataUpdateStruct $cartUpdateStruct`, `CartInterface $cartResult` | | `UpdateEntryEvent` | `CartService::updateEntry` | `CartInterface $cart`, `EntryInterface $entry`, `EntryUpdateStruct $entryUpdateStruct`, `CartInterface $cartResult` | # Shopping list events Editions: LTS Update, Commerce | Event | Dispatched by | Description | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | | [`BeforeCreateShoppingListEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-Event-BeforeCreateShoppingListEvent.html) | [`ShoppingListService::` `createShoppingList()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-ShoppingListServiceInterface.html#method_createShoppingList) | Dispatched before a shopping list is created. Allows to modify or prevent creation. | | [`CreateShoppingListEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-Event-CreateShoppingListEvent.html) | [`ShoppingListService::` `createShoppingList()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-ShoppingListServiceInterface.html#method_createShoppingList) | Dispatched after a shopping list is created. | | [`BeforeUpdateShoppingListEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-Event-BeforeUpdateShoppingListEvent.html) | [`ShoppingListService::` `updateShoppingList()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-ShoppingListServiceInterface.html#method_updateShoppingList) | Dispatched before a shopping list is updated. Allows to modify or prevent update. | | [`UpdateShoppingListEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-Event-UpdateShoppingListEvent.html) | [`ShoppingListService::` `updateShoppingList()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-ShoppingListServiceInterface.html#method_updateShoppingList) | Dispatched after a shopping list is updated. | | [`BeforeDeleteShoppingListEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-Event-BeforeDeleteShoppingListEvent.html) | [`ShoppingListService::` `deleteShoppingList()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-ShoppingListServiceInterface.html#method_deleteShoppingList) | Dispatched before a shopping list is deleted. Allows to prevent deletion. | | [`DeleteShoppingListEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-Event-DeleteShoppingListEvent.html) | [`ShoppingListService::` `deleteShoppingList()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-ShoppingListServiceInterface.html#method_deleteShoppingList) | Dispatched after a shopping list is deleted. | | [`BeforeClearShoppingListEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-Event-BeforeClearShoppingListEvent.html) | [`ShoppingListService::` `clearShoppingList()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-ShoppingListServiceInterface.html#method_clearShoppingList) | Dispatched before a shopping list is cleared. Allows to modify or prevent clearing. | | [`ClearShoppingListEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-Event-ClearShoppingListEvent.html) | [`ShoppingListService::` `clearShoppingList()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-ShoppingListServiceInterface.html#method_clearShoppingList) | Dispatched after a shopping list is cleared. | | [`BeforeAddEntriesEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-Event-BeforeAddEntriesEvent.html) | [`ShoppingListService::` `addEntries()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-ShoppingListServiceInterface.html#method_addEntries) | Dispatched before entries are added to a shopping list. Allows to modify or prevent addition. | | [`AddEntriesEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-Event-AddEntriesEvent.html) | [`ShoppingListService::` `addEntries()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-ShoppingListServiceInterface.html#method_addEntries) | Dispatched after entries are added to a shopping list. | | [`BeforeRemoveEntriesEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-Event-BeforeRemoveEntriesEvent.html) | [`ShoppingListService::` `removeEntries()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-ShoppingListServiceInterface.html#method_removeEntries) | Dispatched before entries are removed from a shopping list. Allows to modify or prevent removal. | | [`RemoveEntriesEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-Event-RemoveEntriesEvent.html) | [`ShoppingListService::` `removeEntries()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-ShoppingListServiceInterface.html#method_removeEntries) | Dispatched after entries are removed from a shopping list. | # Order management events Editions: Commerce | Event | Dispatched by | Properties | | ------------------------ | --------------------------- | ----------------------------------------------------------------------------------------------- | | `BeforeCreateOrderEvent` | `OrderService::createOrder` | `OrderCreateStruct $createStruct` `?OrderInterface $orderResult = null` | | `CreateOrderEvent` | `OrderService::createOrder` | `OrderCreateStruct $createStruct` `OrderInterface $orderResult` | | `BeforeUpdateOrderEvent` | `OrderService::updateOrder` | `OrderInterface $order` `OrderUpdateStruct $updateStruct` `?OrderInterface $orderResult = null` | | `UpdateOrderEvent` | `OrderService::updateOrder` | `OrderInterface $order` `OrderUpdateStruct $updateStruct` `OrderInterface $orderResult` | | `BeforeCancelOrderEvent` | `OrderService::cancelOrder` | `OrderInterface $order` | | `CancelOrderEvent` | `OrderService::cancelOrder` | `OrderInterface $order` | # Payment events Editions: Commerce ## Payments | Event | Dispatched by | Properties | | -------------------------- | ------------------------------- | --------------------------------------------------------------------------------------------------------- | | `BeforeCreatePaymentEvent` | `PaymentService::createPayment` | `PaymentCreateStruct $createStruct` `?PaymentInterface $paymentResult = null` | | `CreatePaymentEvent` | `PaymentService::createPayment` | `PaymentCreateStruct $createStruct` `PaymentInterface $paymentResult` | | `BeforeUpdatePaymentEvent` | `PaymentService::updatePayment` | `PaymentInterface $payment` `PaymentUpdateStruct $updateStruct` `?PaymentInterface $paymentResult = null` | | `UpdatePaymentEvent` | `PaymentService::updatePayment` | `PaymentInterface $payment` `PaymentUpdateStruct $updateStruct` `PaymentInterface $paymentResult` | | `BeforeDeletePaymentEvent` | `PaymentService::DeletePayment` | `PaymentInterface $payment` | | `DeletePaymentEvent` | `PaymentService::DeletePayment` | `PaymentInterface $payment` | ## Payment methods | Event | Dispatched by | Properties | | -------------------------------- | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | `BeforeCreatePaymentMethodEvent` | `PaymentMethodService::createPaymentMethod` | `PaymentMethodCreateStruct $createStruct` `?PaymentMethodInterface $paymentMethodResult = null` | | `CreatePaymentMethodEvent` | `PaymentMethodService::createPaymentMethod` | `PaymentMethodCreateStruct $createStruct` `PaymentMethodInterface $paymentMethodResult` | | `BeforeUpdatePaymentMethodEvent` | `PaymentMethodService::updatePaymentMethod` | `PaymentMethodInterface $paymentMethod` `PaymentMethodUpdateStruct $updateStruct` `?PaymentMethodInterface $paymentMethodResult = null` | | `UpdatePaymentMethodEvent` | `PaymentMethodService::updatePaymentMethod` | `PaymentMethodInterface $paymentMethod` `PaymentMethodUpdateStruct $updateStruct` `PaymentMethodInterface $paymentMethodResult` | | `BeforeDeletePaymentMethodEvent` | `PaymentMethodService::DeletePaymentMethod` | `PaymentMethodInterface $paymentMethod` | | `DeletePaymentMethodEvent` | `PaymentMethodService::DeletePaymentMethod` | `PaymentMethodInterface $paymentMethod` | # Language events | Event | Dispatched by | Properties | | ------------------------------- | ------------------------------------- | -------------------------------------------------------------------- | | `BeforeCreateLanguageEvent` | `LanguageService::createLanguage` | `LanguageCreateStruct $languageCreateStruct`, \`Language | | `CreateLanguageEvent` | `LanguageService::createLanguage` | `Language $language`, `LanguageCreateStruct $languageCreateStruct` | | `BeforeUpdateLanguageNameEvent` | `LanguageService::updateLanguageName` | `Language $language`, `string $newName`, \`Language | | `UpdateLanguageNameEvent` | `LanguageService::updateLanguageName` | `Language $updatedLanguage`, `Language $language`, `string $newName` | | `BeforeDeleteLanguageEvent` | `LanguageService::deleteLanguage` | `Language $language` | | `DeleteLanguageEvent` | `LanguageService::deleteLanguage` | `Language $language` | ## Enabling languages | Event | Dispatched by | Properties | | ---------------------------- | ---------------------------------- | -------------------------------------------------- | | `BeforeEnableLanguageEvent` | `LanguageService::enableLanguage` | `Language $language`, \`Language | | `EnableLanguageEvent` | `LanguageService::enableLanguage` | `Language $enabledLanguage`, `Language $language` | | `BeforeDisableLanguageEvent` | `LanguageService::disableLanguage` | `Language $language`, \`Language | | `DisableLanguageEvent` | `LanguageService::disableLanguage` | `Language $disabledLanguage`, `Language $language` | # Section events | Event | Dispatched by | Properties | | -------------------------- | ------------------------------- | ----------------------------------------------------------------------------------------- | | `BeforeCreateSectionEvent` | `SectionService::createSection` | `SectionCreateStruct $sectionCreateStruct`, \`Section | | `CreateSectionEvent` | `SectionService::createSection` | `SectionCreateStruct $sectionCreateStruct`, `Section $section` | | `BeforeDeleteSectionEvent` | `SectionService::deleteSection` | `Section $section` | | `DeleteSectionEvent` | `SectionService::deleteSection` | `Section $section` | | `BeforeUpdateSectionEvent` | `SectionService::updateSection` | `Section $section`, `SectionUpdateStruct $sectionUpdateStruct`, \`Section | | `UpdateSectionEvent` | `SectionService::updateSection` | `Section $section`, `SectionUpdateStruct $sectionUpdateStruct`, `Section $updatedSection` | ## Assigning sections | Event | Dispatched by | Properties | | ----------------------------------- | ---------------------------------------- | ---------------------------------------------- | | `BeforeAssignSectionEvent` | `SectionService::assignSection` | `ContentInfo $contentInfo`, `Section $section` | | `AssignSectionEvent` | `SectionService::assignSection` | `ContentInfo $contentInfo`, `Section $section` | | `BeforeAssignSectionToSubtreeEvent` | `SectionService::assignSectionToSubtree` | `Location $location`, `Section $section` | | `AssignSectionToSubtreeEvent` | `SectionService::assignSectionToSubtree` | `Location $location`, `Section $section` | # Object state events | Event | Dispatched by | Properties | | ------------------------------ | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | | `BeforeCreateObjectStateEvent` | `ObjectStateService::createObjectState` | `ObjectStateGroup $objectStateGroup`, `ObjectStateCreateStruct $objectStateCreateStruct`, \`ObjectState | | `CreateObjectStateEvent` | `ObjectStateService::createObjectState` | `ObjectState $objectState`, `ObjectStateGroup $objectStateGroup`, `ObjectStateCreateStruct $objectStateCreateStruct` | | `BeforeUpdateObjectStateEvent` | `ObjectStateService::updateObjectState` | `ObjectState $objectState`, `ObjectStateUpdateStruct $objectStateUpdateStruct`, \`ObjectState | | `UpdateObjectStateEvent` | `ObjectStateService::updateObjectState` | `ObjectState $updatedObjectState`, `ObjectState $objectState`, `ObjectStateUpdateStruct $objectStateUpdateStruct` | | `BeforeDeleteObjectStateEvent` | `ObjectStateService::deleteObjectState` | `ObjectState $objectState` | | `DeleteObjectStateEvent` | `ObjectStateService::deleteObjectState` | `ObjectState $objectState` | ## Object state groups | Event | Dispatched by | Properties | | ----------------------------------- | -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | | `BeforeCreateObjectStateGroupEvent` | `ObjectStateService::createObjectStateGroup` | `ObjectStateGroupCreateStruct $objectStateGroupCreateStruct`, \`ObjectStateGroup | | `CreateObjectStateGroupEvent` | `ObjectStateService::createObjectStateGroup` | `ObjectStateGroup $objectStateGroup`, `ObjectStateGroupCreateStruct $objectStateGroupCreateStruct` | | `BeforeUpdateObjectStateGroupEvent` | `ObjectStateService::updateObjectStateGroup` | `ObjectStateGroup $objectStateGroup`, `ObjectStateGroupUpdateStruct $objectStateGroupUpdateStruct`, \`ObjectStateGroup | | `UpdateObjectStateGroupEvent` | `ObjectStateService::updateObjectStateGroup` | `ObjectStateGroup $updatedObjectStateGroup`, `ObjectStateGroup $objectStateGroup`, `ObjectStateGroupUpdateStruct $objectStateGroupUpdateStruct` | | `BeforeDeleteObjectStateGroupEvent` | `ObjectStateService::deleteObjectStateGroup` | `ObjectStateGroup $objectStateGroup` | | `DeleteObjectStateGroupEvent` | `ObjectStateService::deleteObjectStateGroup` | `ObjectStateGroup $objectStateGroup` | ## Setting states | Event | Dispatched by | Properties | | ------------------------------------- | ---------------------------------------------- | -------------------------------------------------------------------------------------------- | | `BeforeSetContentStateEvent` | `ObjectStateService::deleteObjectState` | `ContentInfo $contentInfo`, `ObjectStateGroup $objectStateGroup`, `ObjectState $objectState` | | `SetContentStateEvent` | `ObjectStateService::deleteObjectState` | `ContentInfo $contentInfo`, `ObjectStateGroup $objectStateGroup`, `ObjectState $objectState` | | `BeforeSetPriorityOfObjectStateEvent` | `ObjectStateService::setPriorityOfObjectState` | `ObjectState $objectState`, `private $priority` | | `SetPriorityOfObjectStateEvent` | `ObjectStateService::setPriorityOfObjectState` | `ObjectState $objectState`, `private $priority` | # Taxonomy events The following Events are dispatched when managing [taxonomy entries](https://doc.ibexa.co/en/latest/content_management/taxonomy/taxonomy/index.md). | Event | Dispatched by | Properties | | ----------------------------------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | `BeforeCreateTaxonomyEntryEvent` | `TaxonomyService::createEntry` | `TaxonomyEntryCreateStruct $createStruct`, `?TaxonomyEntry $taxonomyEntry = null` | | `CreateTaxonomyEntryEvent` | `TaxonomyService::createEntry` | `TaxonomyEntry $taxonomyEntry`, `TaxonomyEntryCreateStruct $createStruct` | | `BeforeMoveTaxonomyEntryEvent` | `TaxonomyService::moveEntry` | `TaxonomyEntry $taxonomyEntry`, `TaxonomyEntry $newParent` | | `MoveTaxonomyEntryEvent` | `TaxonomyService::moveEntry` | `TaxonomyEntry $taxonomyEntry`, `TaxonomyEntry $newParent` | | `BeforeMoveTaxonomyEntryRelativeToSiblingEvent` | `TaxonomyService::moveEntryRelativeToSibling` | `TaxonomyEntry $taxonomyEntry`, `TaxonomyEntry $sibling`, `string $position` | | `MoveTaxonomyEntryRelativeToSiblingEvent` | `TaxonomyService::moveEntryRelativeToSibling` | `TaxonomyEntry $taxonomyEntry`, `TaxonomyEntry $sibling`, `string $position` | | `BeforeRemoveTaxonomyEntryEvent` | `TaxonomyService::removeEntry` | `TaxonomyEntry $taxonomyEntry` | | `RemoveTaxonomyEntryEvent` | `TaxonomyService::removeEntry` | `TaxonomyEntry $taxonomyEntry` | | `BeforeUpdateTaxonomyEntryEvent` | `TaxonomyService::updateEntry` | `TaxonomyEntry $taxonomyEntry`, `TaxonomyEntryUpdateStruct $updateStruct`, `?TaxonomyEntry $updatedTaxonomyEntry = null` | | `UpdateTaxonomyEntryEvent` | `TaxonomyService::updateEntry` | `TaxonomyEntry $updatedTaxonomyEntry`, `TaxonomyEntry $taxonomyEntry`, `TaxonomyEntryUpdateStruct $updateStruct` | # Role events | Event | Dispatched by | Properties | | ----------------------------- | ------------------------------- | ------------------------------------------------------------------------------------------- | | `BeforeCreateRoleDraftEvent` | `RoleService::createRoleDraft` | `Role $role`, \`RoleDraft | | `CreateRoleDraftEvent` | `RoleService::createRoleDraft` | `Role $role`, `RoleDraft $roleDraft` | | `BeforeCreateRoleEvent` | `RoleService::createRole` | `RoleCreateStruct $roleCreateStruct`, \`RoleDraft | | `CreateRoleEvent` | `RoleService::createRole` | `RoleCreateStruct $roleCreateStruct`, `RoleDraft $roleDraft` | | `BeforeUpdateRoleDraftEvent` | `RoleService::updateRoleDraft` | `RoleDraft $roleDraft`, `RoleUpdateStruct $roleUpdateStruct`, \`RoleDraft | | `UpdateRoleDraftEvent` | `RoleService::updateRoleDraft` | `RoleDraft $roleDraft`, `RoleUpdateStruct $roleUpdateStruct`, `RoleDraft $updatedRoleDraft` | | `BeforeCopyRoleEvent` | `RoleService::copyRole` | `Role $role`, `RoleCopyStruct $roleCopyStruct`, \`Role | | `CopyRoleEvent` | `RoleService::copyRole` | `Role $copiedRole`, `Role $role`, `RoleCopyStruct $roleCopyStruct` | | `BeforePublishRoleDraftEvent` | `RoleService::publishRoleDraft` | `RoleDraft $roleDraft` | | `PublishRoleDraftEvent` | `RoleService::publishRoleDraft` | `RoleDraft $roleDraft` | | `BeforeDeleteRoleDraftEvent` | `RoleService::deleteRoleDraft` | `RoleDraft $roleDraft` | | `DeleteRoleDraftEvent` | `RoleService::deleteRoleDraft` | `RoleDraft $roleDraft` | | `BeforeDeleteRoleEvent` | `RoleService::deleteRole` | `Role $role` | | `DeleteRoleEvent` | `RoleService::deleteRole` | `Role $role` | ## Adding policies | Event | Dispatched by | Properties | | ------------------------------------ | -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | `BeforeAddPolicyByRoleDraftEvent` | `RoleService::addPolicyByRoleDraft` | `RoleDraft $roleDraft`, `PolicyCreateStruct $policyCreateStruct`, \`RoleDraft | | `AddPolicyByRoleDraftEvent` | `RoleService::addPolicyByRoleDraft` | `RoleDraft $roleDraft`, `PolicyCreateStruct $policyCreateStruct`, `private $updatedRoleDraft` | | `BeforeUpdatePolicyByRoleDraftEvent` | `RoleService::updatePolicyByRoleDraft` | `RoleDraft $roleDraft`, `PolicyDraft $policy`, `PolicyUpdateStruct $policyUpdateStruct`, \`PolicyDraft | | `UpdatePolicyByRoleDraftEvent` | `RoleService::updatePolicyByRoleDraft` | `RoleDraft $roleDraft`, `PolicyDraft $policy`, `PolicyUpdateStruct $policyUpdateStruct`, `PolicyDraft $updatedPolicyDraft` | | `BeforeRemovePolicyByRoleDraftEvent` | `RoleService::removePolicyByRoleDraft` | `RoleDraft $roleDraft`, `PolicyDraft $policyDraft`, \`RoleDraft | | `RemovePolicyByRoleDraftEvent` | `RoleService::removePolicyByRoleDraft` | `RoleDraft $roleDraft`, `PolicyDraft $policyDraft`, `RoleDraft $updatedRoleDraft` | ## Assigning roles | Event | Dispatched by | Properties | | ---------------------------------- | ------------------------------------ | --------------------------------------------------------------------------------- | | `BeforeAssignRoleToUserEvent` | `RoleService::assignRoleToUser` | `Role $role`, `User $user`, `Limitation\RoleLimitation $roleLimitation` | | `AssignRoleToUserEvent` | `RoleService::assignRoleToUser` | `Role $role`, `User $user`, `Limitation\RoleLimitation $roleLimitation` | | `BeforeAssignRoleToUserGroupEvent` | `RoleService::assignRoleToUserGroup` | `Role $role`, `UserGroup $userGroup`, `Limitation\RoleLimitation $roleLimitation` | | `AssignRoleToUserGroupEvent` | `RoleService::assignRoleToUserGroup` | `Role $role`, `UserGroup $userGroup`, `Limitation\RoleLimitation $roleLimitation` | | `BeforeRemoveRoleAssignmentEvent` | `RoleService::removeRoleAssignment` | `RoleAssignment $roleAssignment` | | `RemoveRoleAssignmentEvent` | `RoleService::removeRoleAssignment` | `RoleAssignment $roleAssignment` | # User events | Event | Dispatched by | Properties | | ----------------------- | ------------------------- | ------------------------------------------------------------------------- | | `BeforeCreateUserEvent` | `UserService::createUser` | `UserCreateStruct $userCreateStruct`, `array $parentGroups`, \`User | | `CreateUserEvent` | `UserService::createUser` | `UserCreateStruct $userCreateStruct`, `array $parentGroups`, `User $user` | | `BeforeUpdateUserEvent` | `UserService::updateUser` | `User $user`, `UserUpdateStruct $userUpdateStruct`, \`User | | `UpdateUserEvent` | `UserService::updateUser` | `User $user`, `UserUpdateStruct $userUpdateStruct`, `User $updatedUser` | | `BeforeDeleteUserEvent` | `UserService::deleteUser` | `User $user`, \`array | | `DeleteUserEvent` | `UserService::deleteUser` | `User $user`, `array $locations` | ## User groups | Event | Dispatched by | Properties | | ---------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------ | | `BeforeCreateUserGroupEvent` | `UserService::createUserGroup` | `UserGroupCreateStruct $userGroupCreateStruct`, `UserGroup $parentGroup`, \`UserGroup | | `CreateUserGroupEvent` | `UserService::createUserGroup` | `UserGroupCreateStruct $userGroupCreateStruct`, `UserGroup $parentGroup`, `UserGroup $userGroup` | | `BeforeUpdateUserGroupEvent` | `UserService::updateUserGroup` | `UserGroup $userGroup`, `UserGroupUpdateStruct $userGroupUpdateStruct`, \`UserGroup | | `UpdateUserGroupEvent` | `UserService::updateUserGroup` | `UserGroup $userGroup`, `UserGroupUpdateStruct $userGroupUpdateStruct` | | `BeforeDeleteUserGroupEvent` | `UserService::deleteUserGroup` | `UserGroup $userGroup`, \`array | | `DeleteUserGroupEvent` | `UserService::deleteUserGroup` | `UserGroup $userGroup`, `array $locations` | | `BeforeMoveUserGroupEvent` | `UserService::moveUserGroup` | `UserGroup $userGroup`, `UserGroup $newParent` | | `MoveUserGroupEvent` | `UserService::moveUserGroup` | `UserGroup $userGroup`, `UserGroup $newParent` | ## Assigning to user groups | Event | Dispatched by | Properties | | -------------------------------------- | ---------------------------------------- | ------------------------------------ | | `BeforeAssignUserToUserGroupEvent` | `UserService::assignUserToUserGroup` | `User $user`, `UserGroup $userGroup` | | `AssignUserToUserGroupEvent` | `UserService::assignUserToUserGroup` | `User $user`, `UserGroup $userGroup` | | `BeforeUnAssignUserFromUserGroupEvent` | `UserService::unAssignUserFromUserGroup` | `User $user`, `UserGroup $userGroup` | | `UnAssignUserFromUserGroupEvent` | `UserService::unAssignUserFromUserGroup` | `User $user`, `UserGroup $userGroup` | ## Updating User data | Event | Dispatched by | Properties | | ------------------------------- | --------------------------------- | --------------------------------------------------------------------------------- | | `BeforeUpdateUserPasswordEvent` | `UserService::updateUserPassword` | `User $user`, `string $newPassword`, \`User | | `UpdateUserPasswordEvent` | `UserService::updateUserPassword` | `User $user`, `string $newPassword`, `User $updatedUser` | | `BeforeUpdateUserTokenEvent` | `UserService::updateUserToken` | `User $user`, `UserTokenUpdateStruct $userTokenUpdateStruct`, \`User | | `UpdateUserTokenEvent` | `UserService::updateUserToken` | `User $user`, `UserTokenUpdateStruct $userTokenUpdateStruct`, `User $updatedUser` | # Segmentation events Editions: Experience | Event | Dispatched by | Properties | | ------------------------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | | `BeforeCreateSegmentGroupEvent` | `SegmentationService::createSegmentGroup` | `SegmentGroupCreateStruct $createStruct`, `?SegmentGroup $segmentGroupResult = null` | | `CreateSegmentGroupEvent` | `SegmentationService::createSegmentGroup` | `SegmentGroupCreateStruct $createStruct`, `SegmentGroup $segmentGroupResult` | | `BeforeUpdateSegmentGroupEvent` | `SegmentationService::updateSegmentGroup` | `SegmentGroup $segmentGroup`, `SegmentGroupUpdateStruct $updateStruct`, `?SegmentGroup $segmentGroupResult = null` | | `UpdateSegmentGroupEvent` | `SegmentationService::updateSegmentGroup` | `SegmentGroup $segmentGroup`, `SegmentGroupUpdateStruct $updateStruct`, `SegmentGroup $segmentGroupResult` | | `BeforeRemoveSegmentGroupEvent` | `SegmentationService::removeSegmentGroup` | `SegmentGroup $segmentGroup` | | `RemoveSegmentGroupEvent` | `SegmentationService::removeSegmentGroup` | `SegmentGroup $segmentGroup` | | `BeforeCreateSegmentEvent` | `SegmentationService::createSegment` | `SegmentCreateStruct $createStruct`, `?Segment $segmentResult = null` | | `CreateSegmentEvent` | `SegmentationService::createSegment` | `SegmentCreateStruct $createStruct`, `Segment $segmentResult` | | `BeforeUpdateSegmentEvent` | `SegmentationService::updateSegment` | `Segment $segment`, `SegmentUpdateStruct $updateStruct`, `?Segment $segmentResult = null` | | `UpdateSegmentEvent` | `SegmentationService::updateSegment` | `Segment $segment`, `SegmentUpdateStruct $updateStruct`, `Segment $segmentResult` | | `BeforeRemoveSegmentEvent` | `SegmentationService::removeSegment` | `Segment $segment` | | `RemoveSegmentEvent` | `SegmentationService::removeSegment` | `Segment $segment` | ## Assigning segments | Event | Dispatched by | Properties | | ---------------------------------------- | ---------------------------------------------- | ------------------------------- | | `BeforeAssignUserToSegmentEvent.php` | `SegmentationService::assignUserToSegment` | `User $user` `Segment $segment` | | `AssignUserToSegmentEvent.php` | `SegmentationService::assignUserToSegment` | `User $user` `Segment $segment` | | `BeforeUnassignUserFromSegmentEvent.php` | `SegmentationService::unassignUserFromSegment` | `User $user` `Segment $segment` | | `UnassignUserFromSegmentEvent.php` | `SegmentationService::unassignUserFromSegment` | `User $user` `Segment $segment` | # Page events Editions: Experience | Event | Dispatched by | Properties | | ----------------------------- | ----------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | | `AttributeSerializationEvent` | `AttributeSerializationDispatcher::serialize` | `LandingPage\Model\BlockValue $blockValue`, `string $attributeIdentifier`, \`mixed | | `BlockContextEvent` | `BlockService::createBlockContextFromRequest` | `Request $request`, \`BlockContextInterface | | `BlockFragmentRenderEvent` | `BlockRenderOptionsFragmentRenderer::dispatchFragmentRenderEvent` | `Content $content`, \`Location | | `BlockResponseEvent` | `BlockResponseSubscriber::getSubscribedEvents` | `BlockContextInterface $blockContext`, `LandingPage\Model\BlockValue $blockValue`, `Request $request`, `Response $response` | | `CollectBlockRelationsEvent` | `CollectRelationsSubscriber::onCollectBlockRelations` | `LandingPage\Value $fieldValue`, `LandingPage\Model\BlockValue $blockValue`, `int[] $relations` | | `PageRenderEvent` | `PageService::dispatchRenderPageEvent` | `Content $content`, \`Location | ## Page Builder The following events are dispatched when editing a page in the Page Builder. | Event | Dispatched by | Properties | | ------------------------------ | ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `BlockConfigurationViewEvent` | `BlockController::dispatchBlockConfigurationViewEvent` | `BlockConfigurationView $blockConfigurationView`, `BlockDefinition $blockDefinition`, `BlockConfiguration $blockConfiguration`, `FormInterface $blockConfigurationForm` | | `BlockPreviewPageContextEvent` | `PreviewController::dispatchPageContextEvent` | `BlockContextInterface $blockContext`, `LandingPage\Model\Page $page`, `array $pagePreviewParameters` | | `BlockPreviewResponseEvent` | `PreviewController::dispatchBlockPreviewResponseEvent` | `BlockContextInterface $blockContext`, `array $pagePreviewParameters`, `LandingPage\Model\Page $page`, `BlockValue $blockValue`, `array $responseData` | ## Page blocks The following events are dispatched when editing a page block. | Event | Dispatched by | Properties | | ------------------------------- | -------------------------------------------- | -------------------------------------------------------------------------------------------------------- | | `BlockDefinitionEvent` | `BlockDefinitionFactory::getBlockDefinition` | `BlockDefinition $definition`, `array $configuration` | | `BlockAttributeDefinitionEvent` | `BlockDefinitionFactory::getBlockDefinition` | `BlockAttributeDefinition $definition`, `array $configuration` | | `PreRenderEvent` | `BlockService::render` | `BlockContextInterface $blockContext`, `BlockValue $blockValue`, `RenderRequestInterface $renderRequest` | | `PostRenderEvent` | `BlockService::render` | `BlockContextInterface $blockContext`, `BlockValue $blockValue`, `string $renderedBlock` | # Site events Editions: Experience The following events are dispatched when managing [Sites](https://doc.ibexa.co/en/latest/multisite/site_factory/site_factory/index.md). | Event | Dispatched by | Properties | | ----------------------- | ------------------------- | ----------------------------------------------------------------------- | | `BeforeCreateSiteEvent` | `SiteService::createSite` | `SiteCreateStruct $siteCreateStruct`, `Site $site` | | `CreateSiteEvent` | `SiteService::createSite` | `Site $site`, `SiteCreateStruct $siteCreateStruct` | | `BeforeUpdateSiteEvent` | `SiteService::updateSite` | `Site $site`, `SiteUpdateStruct $siteUpdateStruct`, \`Site | | `UpdateSiteEvent` | `SiteService::updateSite` | `Site $updatedSite`, `Site $site`, `SiteUpdateStruct $siteUpdateStruct` | | `BeforeDeleteSiteEvent` | `SiteService::deleteSite` | `Site $site` | | `DeleteSiteEvent` | `SiteService::deleteSite` | `Site $site` | # URL events ## URLs | Event | Dispatched by | Properties | | ---------------------- | ----------------------- | -------------------------------------------------------- | | `BeforeUpdateUrlEvent` | `URLService::updateUrl` | `URL $url`, `URLUpdateStruct $struct`, \`URL | | `UpdateUrlEvent` | `URLService::updateUrl` | `URL $url`, `URLUpdateStruct $struct`, `URL $updatedUrl` | ## URL aliases The following events are dispatched when creating and managing [URL aliases](https://doc.ibexa.co/en/latest/content_management/url_management/url_management/#url-aliases). | Event | Dispatched by | Properties | | ----------------------------------------------- | ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | `BeforeCreateGlobalUrlAliasEvent` | `URLAliasService::createGlobalUrlAlias` | `private $resource`, `private $path`, `private $languageCode`, `private $forwarding`, `private $alwaysAvailable`, \`URLAlias | | `CreateGlobalUrlAliasEvent` | `URLAliasService::createGlobalUrlAlias` | `private $resource`, `private $path`, `private $languageCode`, `private $forwarding`, `private $alwaysAvailable`, `URLAlias $urlAlias` | | `BeforeCreateUrlAliasEvent` | `URLAliasService::createUrlAlias` | `Location $location`, `private $path`, `private $languageCode`, `private $forwarding`, `private $alwaysAvailable`, \`URLAlias | | `CreateUrlAliasEvent` | `URLAliasService::createUrlAlias` | `Location $location`, `private $path`, `private $languageCode`, `private $forwarding`, `private $alwaysAvailable`, `URLAlias $urlAlias` | | `BeforeRefreshSystemUrlAliasesForLocationEvent` | `URLAliasService::refreshSystemUrlAliasesForLocation` | `Location $location` | | `RefreshSystemUrlAliasesForLocationEvent` | `URLAliasService::refreshSystemUrlAliasesForLocation` | `Location $location` | | `BeforeRemoveAliasesEvent` | `URLAliasService::removeAliases` | `array $aliasList` | | `RemoveAliasesEvent` | `URLAliasService::removeAliases` | `array $aliasList` | ## URL wildcards The following events are dispatched when creating and managing [URL wildcards](https://doc.ibexa.co/en/latest/content_management/url_management/url_management/#url-wildcards). | Event | Dispatched by | Properties | | ---------------------- | ------------------------------- | ----------------------------------------------------------------------------------------------- | | `BeforeCreateEvent` | `URLWildcardService::create` | `private $sourceUrl`, `private $destinationUrl`, `private $forward`, \`URLWildcard | | `CreateEvent` | `URLWildcardService::create` | `private $sourceUrl`, `private $destinationUrl`, `private $forward`, `URLWildcard $urlWildcard` | | `BeforeUpdateEvent` | `URLWildcardService::update` | `URLWildcard $urlWildcard`, `URLWildcardUpdateStruct $updateStruct` | | `UpdateEvent` | `URLWildcardService::update` | `URLWildcard $urlWildcard`, `URLWildcardUpdateStruct $updateStruct` | | `BeforeTranslateEvent` | `URLWildcardService::translate` | `private $url`, \`URLWildcardTranslationResult | | `TranslateEvent` | `URLWildcardService::translate` | `private $url`, `URLWildcardTranslationResult $result` | | `BeforeRemoveEvent` | `URLWildcardService::remove` | `URLWildcard $urlWildcard` | | `RemoveEvent` | `URLWildcardService::remove` | `URLWildcard $urlWildcard` | # Trash events The following events are dispatched when managing Trash. | Event | Dispatched by | Properties | | ---------------------------- | ------------------------------- | --------------------------------------------------------------------------- | | `BeforeDeleteTrashItemEvent` | `TrashService::deleteTrashItem` | `TrashItem $trashItem`, \`TrashItemDeleteResult | | `DeleteTrashItemEvent` | `TrashService::deleteTrashItem` | `TrashItem $trashItem`, `TrashItemDeleteResult $result` | | `BeforeEmptyTrashEvent` | `TrashService::emptyTrash` | \`TrashItemDeleteResultList | | `EmptyTrashEvent` | `TrashService::emptyTrash` | `TrashItemDeleteResultList $resultList` | | `BeforeRecoverEvent` | `TrashService::recover` | `TrashItem $trashItem`, `Location $newParentLocation`, \`Location | | `RecoverEvent` | `TrashService::recover` | `TrashItem $trashItem`, `Location $newParentLocation`, `Location $location` | | `BeforeTrashEvent` | `TrashService::trash` | `Location $location`, \`TrashItem | | `TrashEvent` | `TrashService::trash` | `Location $location`, \`TrashItem | # Twig Components events Use the events to hook into the rendering process of [Twig Components](https://doc.ibexa.co/en/latest/templating/components/index.md). ## Twig Component rendering | Event | Dispatched by | Description | | ------------------- | -------------------------------------------------------------------------- | ------------------------------------------------ | | `RenderGroupEvent` | `\Ibexa\TwigComponents\Component\Renderer\DefaultRenderer::renderGroup()` | Dispatched before a Component group is rendered | | `RenderSingleEvent` | `\Ibexa\TwigComponents\Component\Renderer\DefaultRenderer::renderSingle()` | Dispatched before a single Component is rendered | # AI Actions events ## AI Action execution | Event | Dispatched by | | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | [BeforeExecuteEvent](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-Event-BeforeExecuteEvent.html) | [ActionServiceInterface](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionServiceInterface.html) | | [ExecuteEvent](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-Event-ExecuteEvent.html) | [ActionServiceInterface](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionServiceInterface.html) | ## Action Configurations management | Event | Dispatched by | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [BeforeCreateActionConfigurationEvent](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-Event-BeforeCreateActionConfigurationEvent.html) | [ActionConfigurationServiceInterface](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfigurationServiceInterface.html) | | [CreateActionConfigurationEvent](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-Event-CreateActionConfigurationEvent.html) | [ActionConfigurationServiceInterface](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfigurationServiceInterface.html) | | [BeforeUpdateActionConfigurationEvent](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-Event-BeforeUpdateActionConfigurationEvent.html) | [ActionConfigurationServiceInterface](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfigurationServiceInterface.html) | | [UpdateActionConfigurationEvent](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-Event-UpdateActionConfigurationEvent.html) | [ActionConfigurationServiceInterface](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfigurationServiceInterface.html) | | [BeforeDeleteActionConfigurationEvent](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-Event-BeforeDeleteActionConfigurationEvent.html) | [ActionConfigurationServiceInterface](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfigurationServiceInterface.html) | | [DeleteActionConfigurationEvent](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-Event-DeleteActionConfigurationEvent.html) | [ActionConfigurationServiceInterface](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfigurationServiceInterface.html) | ## Others | Event | Dispatched by | Description | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [ContextEvent](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Events-ContextEvent.html) | [ActionServiceInterface](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionServiceInterface.html) | Pass additional options to the System Context before an AI Action is executed | | [ResolveActionConfigurationWidgetConfigEvent](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Events-ResolveActionConfigurationWidgetConfigEvent.html) | `\Ibexa\ConnectorAi\Twig\ActionConfigurationWidgetConfigExtension::renderActionConfigurationWidgetConfig()` | Modify the Action Type configuration returned from the [ibexa_ai_config Twig function](https://doc.ibexa.co/en/latest/templating/twig_function_reference/ai_actions_twig_functions/index.md) | | [ResolveActionHandlerEvent](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Events-ResolveActionHandlerEvent.html) | [ActionHandlerResolverInterface](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-ActionHandlerResolverInterface.html) | Hook into the process of choosing a Handler to execute an AI Action | # Discounts events Editions: Commerce ## Discount management The events below are dispatched when managing [discounts](https://doc.ibexa.co/en/latest/discounts/discounts/index.md): | Event | Dispatched by | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [`BeforeCreateDiscountEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-BeforeCreateDiscountEvent.html) | [`DiscountServiceInterface::createDiscount()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountServiceInterface.html#method_createDiscount) | | [`CreateDiscountEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-CreateDiscountEvent.html) | [`DiscountServiceInterface::createDiscount()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountServiceInterface.html#method_createDiscount) | | [`BeforeEnableDiscountEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-BeforeEnableDiscountEvent.html) | [`DiscountServiceInterface::enableDiscount()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountServiceInterface.html#method_enableDiscount) | | [`EnableDiscountEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-EnableDiscountEvent.html) | [`DiscountServiceInterface::enableDiscount()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountServiceInterface.html#method_enableDiscount) | | [`BeforeDisableDiscountEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-BeforeDisableDiscountEvent.html) | [`DiscountServiceInterface::disableDiscount()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountServiceInterface.html#method_disableDiscount) | | [`DisableDiscountEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-DisableDiscountEvent.html) | [`DiscountServiceInterface::disableDiscount()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountServiceInterface.html#method_disableDiscount) | | [`BeforeDeleteDiscountEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-BeforeDeleteDiscountEvent.html) | [`DiscountServiceInterface::deleteDiscount()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountServiceInterface.html#method_deleteDiscount) | | [`DeleteDiscountEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-DeleteDiscountEvent.html) | [`DiscountServiceInterface::deleteDiscount()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountServiceInterface.html#method_deleteDiscount) | | [`BeforeUpdateDiscountEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-BeforeUpdateDiscountEvent.html) | [`DiscountServiceInterface::updateDiscount()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountServiceInterface.html#method_updateDiscount) | | [`UpdateDiscountEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-UpdateDiscountEvent.html) | [`DiscountServiceInterface::updateDiscount()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountServiceInterface.html#method_updateDiscount) | ## Form events ### Form The events below allow you to [customize the discounts creation wizard](https://doc.ibexa.co/en/latest/discounts/extend_discounts_wizard/index.md). | Event | Dispatched by | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [`CreateDiscountCreateStructEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-CreateDiscountCreateStructEvent.html) | [`DiscountFormMapperInterface::mapCreateDataToStruct()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountFormMapperInterface.html#method_mapCreateDataToStruct) | | [`CreateDiscountUpdateStructEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-CreateDiscountUpdateStructEvent.html) | [`DiscountFormMapperInterface::mapUpdateDataToStruct()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountFormMapperInterface.html#method_mapUpdateDataToStruct) | | [`CreateFormDataEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-CreateFormDataEvent.html) | [`DiscountFormMapperInterface::createFormData()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountFormMapperInterface.html#method_createFormData) | | [`MapDiscountToFormDataEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-MapDiscountToFormDataEvent.html) | [`DiscountFormMapperInterface::mapDiscountToFormData()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountFormMapperInterface.html#method_mapDiscountToFormData) | ### Form steps The following events are dispatched when rendering each step of the discount wizard, allowing you to add new fields to it: | Event | Event name | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | | [`CreateFormDataEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-Step-CreateFormDataEvent.html) | `ibexa.discounts.form_mapper..create_form_data` | | [`MapCreateDataToStructEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-Step-MapCreateDataToStructEvent.html) | `ibexa.discounts.form_mapper..map_create_data_to_struct` | | [`MapDiscountToFormDataEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-Step-MapDiscountToFormDataEvent.html) | `ibexa.discounts.form_mapper..map_discount_to_form_data` | | [`MapUpdateDataToStructEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-Step-MapUpdateDataToStructEvent.html) | `ibexa.discounts.form_mapper..map_update_data_to_struct` | The event classes are shared between steps, but they are dispatched with different names. Each step form mapper dispatches its own set of events. You can use the names specified above or generate them using the `createEventName` method, for example `CreateFormDataEvent::createEventName(GeneralPropertiesInterface::IDENTIFIER)` returns `ibexa.discounts.form_mapper.general_properties.create_form_data`. | Form mapper | Step identifier | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | [`ConditionsMapperInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-FormMapper-ConditionsMapperInterface.html) | [`conditions`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-Form-Data-ConditionsInterface.html#constant_IDENTIFIER) | | [`GeneralPropertiesMapperInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-FormMapper-GeneralPropertiesMapperInterface.html) | [`general_properties`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-Form-Data-GeneralPropertiesInterface.html#constant_IDENTIFIER) | | [`ProductConditionsMapperInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-FormMapper-ProductConditionsMapperInterface.html) | [`products`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-Form-Data-ProductConditionInterface.html#constant_IDENTIFIER) | | [`UserConditionsMapperInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-FormMapper-UserConditionsMapperInterface.html) | [`target_group`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-Form-Data-UserConditionInterface.html#constant_IDENTIFIER) | ### Back office These events are dispatched by the back office controllers after user chooses the "Save" action when creating or updating a discount. | Event | Dispatched by | Description | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------ | | [`PreDiscountCreateEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-Form-Event-PreDiscountCreateEvent.html) | `Ibexa\Bundle\Discounts\Controller\DiscountCreateController` | Dispatched when the discount creation is finished in the back office form | | [`PreDiscountUpdateEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-Form-Event-PreDiscountUpdateEvent.html) | `Ibexa\Bundle\Discounts\Controller\DiscountEditController` | Dispatched when the discount modifications is finished in the back office form | ## Discount codes The event below allows you to inject your custom logic before the discount code is applied to a product in cart: | Event | Dispatched by | Description | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | -------------------------------------------------------- | | [`BeforeDiscountCodeApplyEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-DiscountsCodes-Event-BeforeDiscountCodeApplyEvent.html) | `Ibexa\Bundle\DiscountsCodes\Controller\REST\DiscountCodeController` | Dispatched before a discount code is applied in the cart | # Collaboration events You can use the following events to extend the [Collaborative editing feature](https://doc.ibexa.co/en/latest/content_management/collaborative_editing/collaborative_editing/index.md): ## Invitation events | Event | Dispatched by | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [BeforeCreateInvitationEvent](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Invitation-Event-BeforeCreateInvitationEvent.html) | [InvitationService::createInvitation()](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-InvitationServiceInterface.html#method_createInvitation) | | [BeforeDeleteInvitationEvent](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Invitation-Event-BeforeDeleteInvitationEvent.html) | [InvitationService::deleteInvitation()](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-InvitationServiceInterface.html#method_deleteInvitation) | | [BeforeUpdateInvitationEvent](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Invitation-Event-BeforeUpdateInvitationEvent.html) | [InvitationService::updateInvitation()](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-InvitationServiceInterface.html#method_updateInvitation) | | [CreateInvitationEvent](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Invitation-Event-CreateInvitationEvent.html) | [InvitationService::createInvitation()](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-InvitationServiceInterface.html#method_createInvitation) | | [DeleteInvitationEvent](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Invitation-Event-DeleteInvitationEvent.html) | [InvitationService::deleteInvitation()](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-InvitationServiceInterface.html#method_deleteInvitation) | | [UpdateInvitationEvent](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Invitation-Event-UpdateInvitationEvent.html) | [InvitationService::updateInvitation()](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-InvitationServiceInterface.html#method_updateInvitation) | ## Participant events | Event | Dispatched by | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [AddParticipantEvent](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Participant-Event-AddParticipantEvent.html) | [SessionService::addParticipant()](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-SessionServiceInterface.html#method_addParticipant) | | [BeforeAddParticipantEvent](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Participant-Event-BeforeAddParticipantEvent.html) | [SessionService::addParticipant()](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-SessionServiceInterface.html#method_addParticipant) | | [BeforeRemoveParticipantEvent](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Participant-Event-BeforeRemoveParticipantEvent.html) | [SessionService::removeParticipant()](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-SessionServiceInterface.html#method_removeParticipant) | | [BeforeUpdateParticipantEvent](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Participant-Event-BeforeUpdateParticipantEvent.html) | [SessionService::updateParticipant()](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-SessionServiceInterface.html#method_updateParticipant) | | [RemoveParticipantEvent](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Participant-Event-RemoveParticipantEvent.html) | [SessionService::removeParticipant()](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-SessionServiceInterface.html#method_removeParticipant) | | [UpdateParticipantEvent](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Participant-Event-UpdateParticipantEvent.html) | [SessionService::updateParticipant()](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-SessionServiceInterface.html#method_updateParticipant) | ## Session events | Event | Dispatched by | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [BeforeCreateSessionEvent](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Session-Event-BeforeCreateSessionEvent.html) | [SessionService::createSession()](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-SessionServiceInterface.html#method_createSession) | | [BeforeDeleteSessionEvent](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Session-Event-BeforeDeleteSessionEvent.html) | [SessionService::deleteSession()](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-SessionServiceInterface.html#method_deleteSession) | | [BeforeUpdateSessionEvent](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Session-Event-BeforeUpdateSessionEvent.html) | [SessionService::updateSession()](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-SessionServiceInterface.html#method_updateSession) | | [CreateSessionEvent](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Session-Event-CreateSessionEvent.html) | [SessionService::createSession()](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-SessionServiceInterface.html#method_createSession) | | [DeleteSessionEvent](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Session-Event-DeleteSessionEvent.html) | [SessionService::deleteSession()](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-SessionServiceInterface.html#method_deleteSession) | | [JoinSessionEvent](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Session-Event-JoinSessionEvent.html) | `SessionJoinController::joinAction()` | | [LeaveSessionEvent](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Session-Event-LeaveSessionEvent.html) | `SessionLeaveController::leaveAction()` | | [UpdateSessionEvent](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Session-Event-UpdateSessionEvent.html) | [SessionService::updateSession()](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-SessionServiceInterface.html#method_updateSession) | | [SessionPublicPreviewEvent](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Session-Event-SessionPublicPreviewEvent.html) | `SessionPublicPreviewController::previewAction()` | ## User filtering events | Event | Dispatched by | Description | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------------------- | | [`UsersWithPermissionInfoMappedEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Share-Event-UsersWithPermissionInfoMappedEvent.html) | `Ibexa\Share\Permission\Mapper\` `UsersWithPermissionInfoMapper` | Allows further filtering of users with permissions for Collaborative editing | # Integrated help events Editions: LTS Update ## Product tour events The following event is dispatched when rendering a [product tour scenario](https://doc.ibexa.co/en/latest/administration/back_office/product_tour/index.md). | Event | Dispatched by | | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | | [`RenderProductTourScenarioEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-IntegratedHelp-Event-RenderProductTourScenarioEvent.html) | `Ibexa\IntegratedHelp\Renderer\ProductTourRenderer::render()` | To learn how you can use this event to customize your product tour scenarios, see [Customize product tour](https://doc.ibexa.co/en/latest/administration/back_office/customize_product_tour/index.md). # Other events ## Bookmarks The following events are dispatched when adding content items to bookmarks. | Event | Dispatched by | Properties | | --------------------------- | --------------------------------- | -------------------- | | `BeforeCreateBookmarkEvent` | `BookmarkService::createBookmark` | `Location $location` | | `CreateBookmarkEvent` | `BookmarkService::createBookmark` | `Location $location` | | `BeforeDeleteBookmarkEvent` | `BookmarkService::deleteBookmark` | `Location $location` | | `DeleteBookmarkEvent` | `BookmarkService::deleteBookmark` | `Location $location` | ## Notifications The following events refer to [notifications displayed in the user menu](https://doc.ibexa.co/en/latest/administration/back_office/notifications/#create-custom-notifications). | Event | Dispatched by | Properties | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | | [`BeforeCreateNotificationEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Events-Notification-BeforeCreateNotificationEvent.html) | [`NotificationService::createNotification`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-NotificationService.html#method_createNotification) | `CreateStruct $createStruct`, \`Notification | | [`CreateNotificationEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Events-Notification-CreateNotificationEvent.html) | [`NotificationService::createNotification`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-NotificationService.html#method_createNotification) | `Notification $notification`, `CreateStruct $createStruct` | | [`BeforeDeleteNotificationEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Events-Notification-BeforeDeleteNotificationEvent.html) | [`NotificationService::deleteNotification`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-NotificationService.html#method_deleteNotification) | `Notification $notification` | | [`DeleteNotificationEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Events-Notification-DeleteNotificationEvent.html) | [`NotificationService::deleteNotification`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-NotificationService.html#method_deleteNotification) | `Notification $notification` | | [`BeforeMarkNotificationAsReadEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Events-Notification-BeforeMarkNotificationAsReadEvent.html) | [`NotificationService::markNotificationAsRead`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-NotificationService.html#method_markNotificationAsRead) | `Notification $notification` | | [`MarkNotificationAsReadEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Events-Notification-MarkNotificationAsReadEvent.html) | [`NotificationService::markNotificationAsRead`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-NotificationService.html#method_markNotificationAsRead) | `Notification $notification` | | [`BeforeMarkNotificationAsUnreadEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Events-Notification-BeforeMarkNotificationAsUnreadEvent.html) | [`NotificationService::markNotificationAsUnread`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-NotificationService.html#method_markNotificationAsUnread) | `Notification $notification` | | [`MarkNotificationAsUnreadEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Events-Notification-MarkNotificationAsUnreadEvent.html) | [`NotificationService::markNotificationAsUnread`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-NotificationService.html#method_markNotificationAsUnread) | `Notification $notification` | ## Settings The following events refer to key/value application-wide settings in database. | Event | Dispatched by | Properties | | -------------------------- | ------------------------------- | ----------------------------------------------------------------------------------------- | | `BeforeCreateSettingEvent` | `SettingService::createSetting` | `SettingCreateStruct $settingCreateStruct`, \`Setting | | `CreateSettingEvent` | `SettingService::createSetting` | `Setting $setting`, `SettingCreateStruct $settingCreateStruct` | | `BeforeUpdateSettingEvent` | `SettingService::updateSetting` | `Setting $setting`, `SettingUpdateStruct $settingUpdateStruct`, \`Setting | | `UpdateSettingEvent` | `SettingService::updateSetting` | `Setting $updatedSetting`, `Setting $setting`, `SettingUpdateStruct $settingUpdateStruct` | | `BeforeDeleteSettingEvent` | `SettingService::deleteSetting` | `Setting $setting` | | `DeleteSettingEvent` | `SettingService::deleteSetting` | `Setting $setting` | ## User preferences The following events are dispatched when changing the user settings available in the user menu. | Event | Dispatched by | Properties | | ------------------------------ | ------------------------------------------ | ----------------------------------------------------- | | `BeforeSetUserPreferenceEvent` | `UserPreferenceService::setUserPreference` | `UserPreferenceSetStruct[] $userPreferenceSetStructs` | | `SetUserPreferenceEvent` | `UserPreferenceService::setUserPreference` | `UserPreferenceSetStruct[] $userPreferenceSetStructs` | ## DAM assets | Event | Dispatched by | Properties | | --------------------- | ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | | `PublishVersionEvent` | `PublishAssetEventDispatcher::emitPublishAssetEvent` | `Content $content`, `Connector\Dam\AssetIdentifier $assetIdentifier`, `Connector\Dam\AssetSource $assetSource` | ## Image Editor The following event is dispatched when the Image Editor optimizes an image. You can subscribe to it to customize the list of active image optimizers at runtime. For more information, see [Customizing image optimizers with an event](https://doc.ibexa.co/en/latest/content_management/images/images/#customizing-image-optimizers). | Event | Dispatched by | Properties | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- | ---------------------------------------------------- | | [`ConfigureImageOptimizersEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ImageEditor-Event-ConfigureImageOptimizersEvent.html) | `SpatieChainOptimizer::` `optimize` | `array $optimizers` | ## Form Builder (Experience) (Commerce) | Event | Dispatched by | Properties | | ------------------------------- | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | | `FieldAttributeDefinitionEvent` | `FieldDefinitionFactory::getAttributesDefinitions` | `FieldAttributeDefinitionBuilder $definitionBuilder`, `array $configuration` | | `FieldDefinitionEvent` | `FieldDefinitionFactory::getFieldDefinition` | `FieldDefinitionBuilder $definitionBuilder`, `array $configuration` | | `FieldValidatorDefinitionEvent` | `FieldDefinitionFactory::getValidatorsDefinitions` | `FieldDefinitionBuilder $definitionBuilder`, `array $configuration` | | `FormActionEvent` | `HandleFormSubmission::handleFormSubmission` | `ContentView $contentView`, `Ibexa\Contracts\FormBuilder\FieldType\Model\Form $form`, `string $action`, `mixed $data` | | `FormSubmitEvent` | `HandleFormSubmission::handleFormSubmission` | `ContentView $contentView`, `Ibexa\Contracts\FormBuilder\FieldType\Model\Form $form`, `array $data` | # Administration # Administration Administer and configure your Ibexa DXP installation. - [Admin panel](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/administration/admin_panel/admin_panel/): Ibexa DXP back office contains managements options for permissions, users, languages, content types, and system information. - [Project organization](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/administration/project_organization/project_organization/): An Ibexa DXP project follows Symfony's directory structure to organize files in the project. - [Configuration](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/administration/configuration/configuration/): In Ibexa DXP you store and manage configuration in project files, typically in YAML format. - [Back office](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/administration/back_office/back_office/): Back office holds the administrator and editor interface and allows creating, publishing and managing content, users, settings, and more. # Project organization Ibexa DXP is a Symfony application and follows the project structure used by Symfony. You can see an example of organizing a project in the [companion repository](https://github.com/ezsystems/ezplatform-ee-beginner-tutorial/tree/v3-master) for the [Beginner tutorial](https://doc.ibexa.co/en/latest/tutorials/page_and_form_tutorial/page_and_form_tutorial/index.md). ## PHP code The project's PHP code (for example, controllers or event listeners) should be placed in the `src` folder. Reusable libraries should be packaged so that they can be managed with Composer. ## Templates Project templates should go into the `templates` folder. They can then be referenced in code without any prefix, for example `templates/full/article.html.twig` can be referenced in Twig templates or PHP as `full/article.html.twig`. ## Assets Project assets should go into the `assets` folder. They can be referenced as relative to the root, for example `assets/js/script.js` can be referenced as `js/script.js` from templates. All project assets are accessible through the `assets` path. ## Configuration You project's configuration is placed in the respective files in `config/packages`. For more information, see [Configuration](https://doc.ibexa.co/en/latest/administration/configuration/configuration/index.md). ### Importing configuration from a bundle If you're keeping some of your code in a bundle, dealing with core bundle semantic configuration can be tedious if you maintain it in the main `config/packages/ibexa.yaml` configuration file. You can import configuration from a bundle by following the Symfony tutorial [How to Import Configuration Files/Resources](https://symfony.com/doc/7.4/service_container/import.html). ## Versioning a project The recommended method is to version the whole project repository. # Architecture Ibexa DXP architecture is based on the philosophy to **use APIs** that is maintained in the long term. This **makes upgrades easier and provides lossless couplings** between all parts of the architecture, at the same time improving the migration capabilities of the system. The structure of an Ibexa DXP app is based on the Symfony framework but content management functions rely on the public PHP API. Other applications integrate with Ibexa DXP via REST API, which also relies on the public PHP API. *[Image: Architecture]* The architecture of Ibexa DXP is layered and uses clearly defined APIs between the layers. | Layer | Description | | ------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Back office](https://doc.ibexa.co/en/latest/administration/back_office/back_office_configuration/index.md) | Back office contains all the necessary parts to run the Ibexa DXP back office interface. | | [HTTP Cache](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/http_cache/http_cache/index.md) | Symfony HTTP cache is used to manage content "view" cache with an expiration model. In addition it is extended by using FOSHttpCache to add several advanced features. | | [Controllers](https://doc.ibexa.co/en/latest/templating/queries_and_controllers/controllers/index.md) | Controllers created by you to read information from a Request object, create and return a Response objects. | | [Twig templates](https://doc.ibexa.co/en/latest/templating/twig_function_reference/twig_function_reference/index.md) | Set of custom and built-in Twig templates. User interfaces are developed with the Twig template engine and query the public PHP API directly. | | [REST API v2](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_usage/rest_api_usage/index.md) | The REST API v2 enables you to interact with an Ibexa DXP installation through the HTTP protocol, following a REST interaction model. | | [GraphQL](https://doc.ibexa.co/en/latest/api/graphql/graphql/index.md) | GraphQL for Ibexa DXP exposes the domain model using the repository, based on content type groups, content types and field definitions. | | [Public PHP API](https://doc.ibexa.co/en/latest/api/php_api/php_api/index.md) | Public PHP API exposes the repository which enables you to create, read, update, manage and delete all objects available in Ibexa DXP. | | Business Logic | The business logic is defined in the kernel. This business logic is exposed to applications via an API. It is used to organize development of the user interface layer. | | SPI | Service Provider Interface which defines contracts for implementing various parts of the system, including persistence layer (`SPI\Persistence`), custom field types, custom limitations, and more. | | [Persistence cache](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/persistence_cache/index.md) | The implementation of SPI\\Persistence that decorates the main backend implementation. | | [Search](https://doc.ibexa.co/en/latest/search/search/index.md) | Search API that allows both full-text search and querying the content. | | [SQL Storage Engine](https://doc.ibexa.co/en/latest/search/search_engines/legacy_search_engine/legacy_search_overview/index.md) | Legacy search engine is SQL-based and uses Doctrine's database connection. | | [Solr Storage Engine](https://doc.ibexa.co/en/latest/search/search_engines/solr_search_engine/solr_overview/index.md) | Transparent drop-in replacement for the SQL-based Legacy search engine. | | [IO](https://doc.ibexa.co/en/latest/content_management/file_management/file_management/#native-io-handler) | The IO API is organized around two types of handlers, both used by the IOService. | | [IO Handler](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/clustering/clustering/#dfs-io-handler) | The IO Handler manipulates metadata, making up for the potential inconsistency of network-based filesystems. | | [Recommendation](https://doc.ibexa.co/en/latest/personalization/enable_personalization/index.md) | Recommendation API. | | [Personalization server](https://doc.ibexa.co/en/latest/personalization/enable_personalization/index.md) | Personalization server allows displaying recommendations on the website. | # Bundles A bundle in Symfony (and Ibexa DXP) is a separate part of your application that implements a feature. You can [create bundles yourself](https://doc.ibexa.co/en/latest/resources/contributing/package_structure/index.md) or make use of available open-source bundles. You can also reuse the bundles you create in other projects or share them with the community. Many Ibexa DXP functionalities are provided through separate bundles included in the installation. You can see the bundles that are automatically installed with Ibexa DXP in the respective `composer.json` files. For example, for Ibexa Headless, see the [JSON file on GitHub](https://github.com/ibexa/headless/blob/master/composer.json). ## Working with bundles All bundles containing built-in Ibexa DXP functionalities are installed automatically. Additionally, you can install community-developed bundles from [Ibexa DXP Packages.](https://developers.ibexa.co/packages) To learn how to create your own bundles, see [Symfony documentation on bundles](https://symfony.com/doc/7.4/bundles.html). ### Overriding third-party bundles When you use an external bundle, you can override its parts, such as templates or controllers. To do so, make use of [Symfony's bundle override mechanism](https://symfony.com/doc/7.4/bundles/override.html). When overriding files, the path inside your application has to correspond to the path inside the bundle. ### Removing bundles To remove a bundle (either one you created yourself, or an out-of-the-box one that you don't need), see the [How to Remove a Bundle](https://symfony.com/doc/7.4/bundles/remove.html) instruction in Symfony doc. ## Core packages > **Tip: Tip** > > Ibexa Open Source is composed of the core packages. | Bundle | Description | | ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [ibexa/admin-ui](https://github.com/ibexa/admin-ui) | Back office interface | | [ibexa/admin-ui-assets](https://github.com/ibexa/admin-ui-assets) | Assets for the back office | | [ibexa/content-forms](https://github.com/ibexa/content-forms) | Form-based integration for the Symfony Forms into content and user objects in kernel | | [ibexa/core](https://github.com/ibexa/core) | Core of the Ibexa DXP application | | [ibexa/core-search](https://github.com/ibexa/core-search) | Search-related capabilities | | [ibexa/core-persistence](https://github.com/ibexa/core-persistence) | Core system persistence | | [ibexa/cron](https://github.com/ibexa/cron) | Cron package for use with the `ibexa:cron:run` command | | [ibexa/design-engine](https://github.com/ibexa/design-engine) | [Design fallback system](https://doc.ibexa.co/en/latest/templating/design_engine/design_engine/index.md) | | [ibexa/doctrine-schema](https://github.com/ibexa/doctrine-schema) | Basic abstraction layer for cross-DBMS schema import | | [ibexa/fieldtype-matrix](https://github.com/ibexa/fieldtype-matrix) | [Matrix field type](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/matrixfield/index.md) | | [ibexa/fieldtype-query](https://github.com/ibexa/fieldtype-query) | [Query field type](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/contentqueryfield/index.md) | | [ibexa/fieldtype-richtext](https://github.com/ibexa/fieldtype-richtext) | field type for supporting rich-formatted text stored in a structured XML format | | [ibexa/graphql](https://github.com/ibexa/graphql) | GraphQL server for Ibexa DXP | | [ibexa/http-cache](https://github.com/ibexa/http-cache) | [HTTP cache handling](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/http_cache/http_cache/index.md), using multi tagging | | [ibexa/i18n](https://github.com/ibexa/i18n) | Centralized translations to ease synchronization with Crowdin | | [ibexa/messenger](https://github.com/ibexa/messenger) | [Background and asynchronous task processing](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/background_tasks/index.md) using Symfony Messenger | | [ibexa/notifications](https://github.com/ibexa/notifications) | Sending [notifications](https://doc.ibexa.co/en/latest/administration/back_office/notifications/index.md) | | [ibexa/post-install](https://github.com/ibexa/post-install) | Apache and nginx templates | | [ibexa/rest](https://github.com/ibexa/rest) | REST API | | [ibexa/search](https://github.com/ibexa/search) | Common search functionalities | | [ibexa/solr](https://github.com/ibexa/solr) | [Solr-powered](https://solr.apache.org/) search handler | | [ibexa/standard-design](https://github.com/ibexa/standard-design) | Standard design and theme to be handled by `design-engine` | | [ibexa/system-info](https://github.com/ibexa/system-info) | Information about the system Ibexa DXP is running on | | [ibexa/twig-components](https://github.com/ibexa/twig-components) | [Twig Components](https://doc.ibexa.co/en/latest/templating/components/index.md) | | [ibexa/user](https://github.com/ibexa/user) | User management | ## Ibexa Headless packages | Bundle | Description | | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | ibexa/oss | Core packages | | ibexa/app-switcher | Integration with the [QNTM group](https://qntmgroup.com/solutions/) | | ibexa/calendar | Calendar tab with a calendar widget | | ibexa/collaboration | Collaboration functionality | | ibexa/connect | [Ibexa Connect](https://doc.ibexa.co/projects/connect/en/latest/) | | ibexa/connector-ai | Foundation for the [AI Actions](https://doc.ibexa.co/en/latest/ai_actions/ai_actions/index.md) framework | | ibexa/connector-dam | Connector for DAM (Digital Asset Management) systems | | ibexa/connector-openai | Integrates the AI framework with [OpenAI](https://openai.com) | | ibexa/content-tree | Content tree functionality | | ibexa/elasticsearch | Integration with Elasticsearch search engine | | ibexa/fastly | Fastly support for `http-cache`, for use on Ibexa Cloud or standalone | | ibexa/headless-assets | Assets for the back office | | ibexa/icons | Icon set for the back office | | ibexa/image-editor | [Image Editor](https://doc.ibexa.co/en/latest/content_management/images/configure_image_editor/index.md) | | ibexa/installer | Provides the `ibexa:install` command | | ibexa/measurement | Measurement field type and measurement product catalog attribute | | ibexa/migrations | [Migration of repository data](https://doc.ibexa.co/en/latest/content_management/data_migration/data_migration/index.md) | | ibexa/oauth2-client | Authenticate user through a [third-party OAuth 2 server](https://doc.ibexa.co/en/latest/users/oauth_client/index.md), integration with [`knpuniversity/oauth2-client-bundle`](https://github.com/knpuniversity/oauth2-client-bundle) | | ibexa/oauth2-server | Configure Ibexa DXP to act as a [OAuth2 Server](https://doc.ibexa.co/en/latest/users/oauth_server/index.md) | | ibexa/personalization | Functionality for personalized recommendations | | ibexa/product-catalog-date-time-attribute | Implementation of the [Date and Time attribute type](https://doc.ibexa.co/en/latest/product_catalog/attributes/date_and_time/index.md) | | ibexa/product-catalog-symbol-attribute | Implementation of the [Symbol attribute type](https://doc.ibexa.co/en/latest/product_catalog/attributes/symbol_attribute_type/index.md) | | ibexa/product-catalog | Product catalog functionality | | ibexa/scheduler | Date-based publishing functionality | | ibexa/seo | Search Engine Optimization (SEO) tool | | ibexa/share | Content-sharing functionality | | ibexa/taxonomy | Taxonomy functionality | | ibexa/tree-builder | Tree builder functionality | | ibexa/version-comparison | Enables comparing between two versions of the same field | | ibexa/workflow | Collaboration feature that enables you to send content draft to any user for a review or rewriting | ## Ibexa Experience packages | Bundle | Description | | ----------------------- | -------------------------------------------------------------------------------------------------------------- | | ibexa/headless | Metapackage for Symfony Flex-based Ibexa DXP Headless installation | | ibexa/activity-log | Customer portal and corporate accounts | | ibexa/corporate-account | Customer portal and corporate accounts | | ibexa/dashboard | [Customizable dashboard](https://doc.ibexa.co/en/latest/administration/dashboard/customize_dashboard/index.md) | | ibexa/fieldtype-address | Address handling field type | | ibexa/form-builder | Enables creating Form content items with multiple form fields | | ibexa/page-builder | Page editor | | ibexa/fieldtype-page | Page handling field type | | ibexa/permissions | Additional permission functionalities | | ibexa/segmentation | Segment functionality for profiling the content displayed to specific users | | ibexa/site-factory | Enables configuration of sites from UI | | ibexa/engage | Enables integration with [Qualifio Engage platform](https://developers.qualifio.com/docs/engage/) | ## Ibexa Commerce packages | Bundle | Description | | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | ibexa/experience | Metapackage for Symfony Flex-based Ibexa DXP Experience installation | | ibexa/cart | Main store functionalities | | ibexa/checkout | Store checkout functionality | | ibexa/corporate-account-commerce-bridge | Additional functionality for [corporate accounts](https://doc.ibexa.co/en/latest/administration/admin_panel/corporate_admin_panel/index.md) | | ibexa/discounts | Adds [discounts](https://doc.ibexa.co/en/latest/discounts/discounts/index.md) functionality | | ibexa/discounts-codes | Adds the possibility to use discount codes with the [Discounts](https://doc.ibexa.co/en/latest/discounts/discounts/index.md) functionality | | ibexa/storefront | A storefront starting kit | | ibexa/order-management | Order management | | ibexa/payment | Payment handling | | ibexa/shipping | Shipping handling | | ibexa/connector-payum | [Payum integration](https://doc.ibexa.co/en/latest/commerce/payment/payum_integration/index.md) | ## Optional packages The following packages are optional and can be installed independently. | Bundle | Description | | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | [ibexa/automated-translation](https://github.com/ibexa/automated-translation) | Automated translation of content using [Google Translate or DeepL](https://doc.ibexa.co/en/latest/multisite/languages/automated_translations/index.md) | | ibexa/cdp | Integration with the [Customer Data Platform](https://doc.ibexa.co/en/latest/cdp/cdp/index.md) | | [ibexa/cloud](https://github.com/ibexa/cloud) | Integration with [Ibexa Cloud](https://doc.ibexa.co/en/latest/ibexa_cloud/ibexa_cloud/index.md) | In addition, you can extend the capabitilies of your project by installing additional [LTS Updates](https://doc.ibexa.co/en/latest/ibexa_products/editions/#lts-updates). # Configure default dashboard Editions: Experience You can configure default dashboard under the `ibexa.system..admin_group` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files). Create `ibexa_dashboard.yaml` file in the `config/packages/` directory. The following example configuration defines default dashboard: ``` ibexa: system: admin_group: dashboard: container_remote_id: dashboard_container default_dashboard_remote_id: default_dashboard users_container_remote_id: user_dashboards predefined_container_remote_id: predefined_dashboards section_identifier: dashboard content_type_identifier: dashboard_landing_page container_content_type_identifier: folder ``` Configuration can be set per [SiteAccess](https://doc.ibexa.co/en/latest/multisite/multisite_configuration/#siteaccess-configuration) or [SiteAccess group](https://doc.ibexa.co/en/latest/multisite/multisite_configuration/#siteaccess-groups). All the settings in the configuration are reflected in the back office. ## Container remote ID Defines starting location container for all the dashboards, including customized and predefined ones. You can see it in the **Admin** panel, **Dashboards** section, **Dashboards** folder in the content tree. In the **Technical details** tab, it is defined as **Location remote ID**. *[Image: Container remote ID]* ## Default dashboard remote ID Specifies default predefined dashboard. All the users can see this dashboard as a starting dashboard in the back office. You can see it in the **Admin** panel, **Dashboards** section, **Default dashboard** folder inside of **Predefined dashboards** container in the content tree. In the **Technical details** tab, it's defined as **Location remote ID**. ## Users container remote ID Defines a container for users folders, which contain all customized dashboards. You can see it in the **Admin** panel, **Dashboards** section, **User dashboards** folder inside of main **Dashboards** container in the content tree. In the **Technical details** tab, it's defined as **Location remote ID**. ## Predefined container remote ID Defines a container that contains all predefined dashboards created by Administrator. You can see it in the **Admin** panel, **Dashboards** section, **Predefined dashboards** folder inside of main **Dashboards** container in the content tree. In the **Technical details** tab, it's defined as **Location remote ID**. ## Section identifier Specifies the name of the [Section](https://doc.ibexa.co/en/latest/administration/content_organization/sections/index.md). ## Content type identifier It is an identifier that represents dashboard content type. You can find it in the **Admin** panel, **Dashboard content Type** section, **View/Global properties** tab. *[Image: Content type identifier]* ## Container content type identifier Determines the content type identifier of the container for dashboards and lets you create additional structure for the predefined dashboards. By default all the dashboards containers are set as a folders. *[Image: Container content type]* If the `folder` content type doesn't exist or is modified, you can use another one, for example: ``` ibexa: system: default: dashboard: container_content_type_identifier: user_dashboard_container ``` The custom content type should be a container and needs to have a field type with `name` identifier. # Customize dashboard Editions: Experience > **Info: Info** > > The Dashboard Builder is available only in the Experience and Commerce editions. The dashboard from the Headless edition can be customized using [Twig Components](https://doc.ibexa.co/en/latest/templating/components/index.md). You can customize the dashboard depending on your needs using Dashboard Builder. Customized dashboard displays a set of widgets selected by the user. > **Tip: Tip** > > For detailed instruction on how to customize dashboards with the Dashboard Builder, see [User Documentation](https://doc.ibexa.co/projects/userguide/en/5.0/getting_started/dashboard/work_with_dashboard/#customize-dashboard). ## Manage permissions To customize dashboard, you need to have `dashboard/customize` [policy](https://doc.ibexa.co/en/latest/permissions/permission_overview/index.md). By default, all the users belonging to the `Editors` user group, have `Dashboard`[role](https://doc.ibexa.co/en/latest/administration/admin_panel/roles_admin_panel/index.md) assigned, so they can edit, create, or delete dashboard. If, by any reason, you want to narrow this permission, you can set up specific [limitations](https://doc.ibexa.co/en/latest/permissions/limitations/index.md). ## Add custom layout For new dashboard you need to choose layout which defines the available zones. While opening Dashboard Builder, layout window appears - you can choose one from available layouts. You can also add custom layout that then can be available in Dashboard Builder. For more information, see [Customize storefront layout](https://doc.ibexa.co/en/latest/templating/layout/customize_storefront_layout/index.md). ## Create custom blocks Dashboard Builder provides set of ready-to-use blocks, for example, Common content, Quick actions, or Ibexa DXP News. For more information about available blocks, see [User Documentation](https://doc.ibexa.co/projects/userguide/en/5.0/getting_started/dashboard/dashboard_block_reference/). In addition to existing blocks available in Dashboard Builder, you can also create custom blocks. To do it, follow the instruction on how to [create custom page block](https://doc.ibexa.co/en/latest/content_management/pages/create_custom_page_block/index.md). # DashboardService's PHP API Editions: Experience You can use `DashboardService`'s PHP API to manage custom dashboards. To obtain this service, inject the `Ibexa\Contracts\Dashboard\DashboardServiceInterface`. The service exposes two functions: - `createCustomDashboardDraft(?Location $location = null): Content` - returns a new content item in draft state of `dashboard` content type. If no location is given, it creates a copy of the dashboard of the user currently logged in. If a location is given, it creates a copy with the given location. The default name of the customized dashboard is set as `My dashboard`. This new Content draft is located in the current user custom dashboard container. - `createDashboard(DashboardCreateStruct $dashboardCreateStruct): Content` - publishes the given dashboard creation structure (`Ibexa\Contracts\Dashboard\Values\DashboardCreateStruct`) under `dashboard.predefined_container_remote_id`. ## Customize dashboard using DashboardService The following example is a command deploying a custom dashboard to users of content groups. Using the `admin` account, it loads the group members, logs each one in, creates a custom dashboard by copying a default one, and then publishes the draft version of customized dashboard. First argument is the `Content ID` of the dashboard to copy. Following arguments are the Content IDs of the user groups. ``` locationService = $repository->getLocationService(); $this->contentService = $repository->getContentService(); $this->userService = $repository->getUserService(); $this->permissionResolver = $repository->getPermissionResolver(); parent::__construct(); } public function configure(): void { $this ->addArgument('dashboard', InputArgument::REQUIRED, 'Location ID of the dashboard model') ->addArgument('group', InputArgument::REQUIRED | InputArgument::IS_ARRAY, 'User Group Content ID(s)'); } protected function execute(InputInterface $input, OutputInterface $output): int { $dashboardModelLocationId = (int)$input->getArgument('dashboard'); $userGroupLocationIdList = array_map(intval(...), $input->getArgument('group')); foreach ($userGroupLocationIdList as $userGroupLocationId) { try { $admin = $this->userService->loadUserByLogin('admin'); $this->permissionResolver->setCurrentUserReference($admin); foreach ($this->userService->loadUsersOfUserGroup($this->userService->loadUserGroup($userGroupLocationId)) as $user) { $this->permissionResolver->setCurrentUserReference($user); $dashboardDraft = $this->dashboardService->createCustomDashboardDraft($this->locationService->loadLocation($dashboardModelLocationId)); $this->contentService->publishVersion($dashboardDraft->getVersionInfo()); } } catch (\Throwable $throwable) { dump($throwable); } } return self::SUCCESS; } } ``` The following line runs the command with `74` as the model dashboard's Content ID, `13` the user group's Content ID, and on the SiteAccess `admin` to have the right `user_content_type_identifier` config: ``` php bin/console doc:dashboard 74 13 --siteaccess=admin ``` # Admin panel Once you set up your environment you can start your work as an administrator. You can find key tools in **Admin** panel. To access **Admin** panel, click the icon: *[Image: Admin panel Icon]*. - [Users](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/administration/admin_panel/users_admin_panel/): You can access all users and user groups in the Users tab. - [Roles](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/administration/admin_panel/roles_admin_panel/): To give users an access to your website you need to assign them roles in the Admin Panel. - [URL Management](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/administration/admin_panel/url_management_admin_panel/): URL Management lets you manage external URL addresses and URL wildcards. - [Languages](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/administration/admin_panel/languages_admin_panel/): Ibexa DXP offers the ability to create multiple translations of your website. - [Segments](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/administration/admin_panel/segments_admin_panel/): You can use segments to display specific content to specific users. - [Corporate](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/administration/admin_panel/corporate_admin_panel/): You can manage companies profiles in the Admin Panel. - [Workflow](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/administration/admin_panel/workflow_admin_panel/): The workflow functionality passes a content item version through a series of stages. - [System Information](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/administration/admin_panel/system_information_admin_panel/): System information provides basic system information such as versions of all installed packages. # Users [Users](https://doc.ibexa.co/en/latest/users/users/index.md) in Ibexa DXP are treated the same way as content items. They're organized in groups such as *Guests*, *Editors*, *Anonymous*, which makes it easier to manage them and their permissions. You can access all users and user groups in the **Admin** panel by selecting **Users**. *[Image: Users and user groups]* > **Caution: Caution** > > Be careful not to delete an existing user account. If you do this, content created by this user can be broken and the application can face malfunction. # Roles To give users an access to your website you need to assign them roles in the **Admin** panel. *[Image: Roles]* Each role consists of: **Policies** *[Image: Policies]* Policies are the rules that give users access to different function in a module. You can restrict what user can do with limitations. The available limitations depend on the chosen policy. When policy has more than one limitation, all of them have to apply. See [example use case](https://doc.ibexa.co/en/latest/permissions/permission_use_cases/#restrict-editing-to-part-of-the-tree). > **Note: Note** > > Limitation specifies what a user can do, not what they can't do. A `Location` limitation, for example, gives the user access to content with a specific location, not prohibits it. > > For more information, see [Limitation reference](https://doc.ibexa.co/en/latest/permissions/limitation_reference/index.md). **Assignments** *[Image: Assignments]* After you created all policies, you can assign the role to users and/or user groups with possible additional limitations. Every user or user group can have multiple roles. A user can also belong to many groups, for example, Administrators, Editors, Subscribers. Best practice is to avoid assigning roles to users directly. Model your content (for example, content types, sections, or locations) in a way that can be accessed by generic roles. That way system is be more secure and easier to manage. This approach also improves performance. Role assignments and policies are taken into account during search/load queries. For more information, see [Permissions overview](https://doc.ibexa.co/en/latest/permissions/permissions/index.md) and [Permission use cases](https://doc.ibexa.co/en/latest/permissions/permission_use_cases/index.md). # URL Management You can manage external URL addresses and URL wildcards in the **Admin** panel. Configure URL aliases to have human-readable URL addresses throughout your system. For more information, see [URL management](https://doc.ibexa.co/en/latest/content_management/url_management/url_management/index.md). *[Image: URL Management]* # Languages Ibexa DXP offers the ability to create multiple translations of your website. Which version is shown to a visitor depends on the way your installation is set up. You can add a new language version for the website in the [Admin Panel](https://doc.ibexa.co/en/latest/administration/admin_panel/admin_panel/index.md) in the **Languages** tab. Every new language must have a name and a language code, written in the `xxx-XX` format, for example `eng-GB`. *[Image: Languages]* The multilanguage system operates based on a global translation list that contains all languages available in the installation. After adding a language you may have to reload the application to be able to use it. Depending on your set up, additional configuration may be necessary for the new language to work properly, especially with SiteAccesses. See [Languages](https://doc.ibexa.co/en/latest/multisite/languages/languages/index.md) for further information. # Segments Editions: Experience You can use segments to display specific content to specific [users](https://doc.ibexa.co/en/latest/users/users/index.md). They're used out of the box in the Targeting and Dynamic targeting blocks in the page. You can collect segments in segment groups: *[Image: Segment groups]* Each segment group can contain segments that you can target content for. *[Image: Segment]* You can assign users to segments [through the API](https://doc.ibexa.co/en/latest/users/segment_api/#assigning-users). # Corporate You can manage companies profiles in the **Admin** panel. There, in the **Corporate** section, you can find basic information about existing companies, for example, details, versions, locations, translations, a list of members, billing addresses, and technical details regarding the organization, such as visibility, IDs, or relations. *[Image: Corporate section]* For more information, see [Customer management](https://doc.ibexa.co/projects/userguide/en/5.0/customer_management/manage_customers/). # Workflow The workflow functionality passes a content item version through a series of stages. Each workflow consists of stages and transitions between them. For more information, see [Workflow](https://doc.ibexa.co/en/latest/content_management/workflow/workflow/index.md). *[Image: Workflow]* # System Information The System Information panel in the back office is sourced in the [`ibexa/system-info` repository](https://github.com/ibexa/system-info). There you can also find basic system information such as versions of all installed packages. *[Image: System Information]* # Sections Sections are used to divide content items in the tree into groups that are more manageable by content editors. Division into sections allows you, among others, to set [permissions](https://doc.ibexa.co/en/latest/permissions/permission_overview/index.md) for only a part of the tree. *[Image: Sections screen]* Technically, a section is a number, a name, and an identifier. Content items are placed in sections by being assigned the section ID. One item can be in only one section. When a new content item is created, its section ID is set to the default section (which is usually Standard). When the item is published it is assigned to the same section as its parent. Because content must always be in a section, unassigning happens by choosing a different section to move it into. If a content item has multiple location assignments then it is always the section ID of the item referenced by the parent of the main location that is used. In addition, if the main location of a content item with multiple location assignments is changed then the section ID of that item is updated. When content is moved to a different location, the item itself and all of its subtree are assigned to the section of the new location. It works only for copy and move. Assigning a new section to a parent content item doesn't affect the subtree, meaning that subtree cannot currently be updated this way. Sections can only be removed if no content items are assigned to them. Even then, it should be done carefully. When a section is deleted, it's only its definition itself that is removed. Other references to the section remain and thus the system most likely loses consistency. > **Caution: Caution** > > Removing sections may corrupt permission settings, template output and other things in the system. Section ID numbers aren't recycled. If a section is removed, its ID number cannot be reused when a new section is created. ### Registering users Registration form for your website is placed under this address: /register. By default, new users created in this way are placed in the Guest accounts group. To give your users a possibility to register themselves, follow the instructions on [enabling account registration](https://doc.ibexa.co/en/latest/tutorials/beginner_tutorial/8_enable_account_registration/index.md). # Content types A content type is a base for new content items. It defines what fields are available in the content item. *[Image: Content types]* For example, a new content type called *Article* can have fields such as title, author, body, or image. Based on this content type, you can create any number of content items. Content types are organized into groups. *[Image: Content type groups]* You can add your own groups here to keep your content types in better order. For a full tutorial, see [Add a content type](https://doc.ibexa.co/en/latest/getting_started/first_steps/#add-a-content-type) or follow [User Documentation](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/create_edit_content_types/). For a detailed overview of the content model, see [Content model overview](https://doc.ibexa.co/en/latest/content_management/content_model/index.md). ## Content type metadata Each content type is characterized by a set of metadata which define the general behavior of its instances: **Name** – a user-friendly name that describes the content type. This name is used in the interface, but not internally by the system. It can consist of letters, digits, spaces, and special characters (it's mandatory and the maximum length is 255 characters). > **Note: Note** > > Even if your content type defines a field intended as a name for the content item (for example, a title of an article or product name), don't confuse it with this Name, which is a piece of metadata, not a field. **Identifier** – an identifier for internal use in configuration, for example, files, templates, or PHP code. It must be unique, can only contain lowercase letters, digits, and underscores (it's mandatory and the maximum length is 50 characters). **Description** – a detailed description of the content type (optional). **Content name pattern** – a pattern that defines what name a new content item based on this content type gets. The pattern usually consists of field identifiers that tell the system which fields it should use when generating the name of a content item. Each field identifier has to be surrounded with angle brackets. Text outside the angle brackets is included literally. If no pattern is provided, the system automatically uses the first field (optional). **URL alias name pattern** – a pattern which controls how the virtual URLs of the locations are generated when content items are created based on this content type. Only the last part of the virtual URL is affected. The pattern works in the same way as the content name pattern. Text outside the angle brackets is converted with the selected method of URL transformation. If no pattern is provided, the system automatically uses the name of the content item itself (optional). > **Tip: Changing URL alias and content name patterns** > > If you change the content name pattern or the URL alias name pattern, existing content items cannot be modified automatically. The new pattern is only applied after you modify the content item and save a new version. > > The old URL aliases continue to redirect to the same content items. **Container** – a flag which indicates if content items based on this content type are allowed to have sub-items or not (mainly relevant for actions via the UI, not validated by every PHP API). > **Note: Note** > > This flag was added for convenience and only affects the interface. In other words, it doesn't control any actual low-level logic, it simply controls the way the graphical user interface behaves. **Sort children by default by** – rule for sorting sub-items. If the instances of this content type can serve as containers, their children are sorted according to what is selected here. **Sort children by default in order** – another rule for sorting sub-items. This decides the sort order for the criterion chosen above. **Make content available even with missing translations** – a flag which indicates if content items of this content type should be available even without a corresponding language version. See [Content availability](https://doc.ibexa.co/en/latest/content_management/content_availability/index.md). *[Image: Creating a new content type]* ## Field definitions Aside from the metadata, a content type may contain any number of field definitions (but has to contain at least one). They determine what fields of what field types are included in all content items based on this content type. *[Image: Field definitions]* *[Image: Diagram of an example content type]* > **Note: Note** > > You can assign each field defined in a content type to a group by selecting one of the groups in the Category drop-down. [Available groups can be configured in the content repository](https://doc.ibexa.co/en/latest/administration/configuration/repository_configuration/index.md). > **Caution: Caution** > > In case of content types containing many field types you should be aware of possible memory-related issues with publishing/editing. They're caused by the limitation of how many `$_POST` input variables can be accepted. > > The easiest way to fix them is by increasing the `max_input_vars` value in the `php.ini` configuration file. This solution isn't universally recommended and you're proceeding on your own risk. > > Setting the limit inappropriately may damage your project or cause other issues. You may also experience performance problems with such large content types, in particular when you have many content items. If you're experincing too many issues, consider rearranging your project to avoid them. ## Modifying content types A content type and its field definitions can be modified after creation, even if there are already content items based on it in the system. When a content type is modified, each of its instances are changed as well. If a new field definition is added to a content type, this field appears (empty) in every relevant content item. If a field definition is deleted from the content type, all the corresponding fields are removed from content items of this type. ## Removing content types System content types are by default used for the File Uploads and removing them can cause errors. If you decide to remove a `file` or `image` content type, or change their identifiers, you need to change the configuration, so it reflects the available content types. Example configuration: ``` parameters: ibexa.multifile_upload.location.default_mappings: # Image - mime_types: - image/jpeg - image/jpg - image/pjpeg - image/pjpg - image/png - image/bmp - image/gif - image/tiff - image/x-icon - image/webp content_type_identifier: custom_image_contenttype content_field_identifier: image name_field_identifier: name # File - mime_types: - image/svg+xml - application/msword - application/vnd.openxmlformats-officedocument.wordprocessingml.document - application/vnd.ms-excel - application/vnd.openxmlformats-officedocument.spreadsheetml.sheet - application/vnd.ms-powerpoint - application/vnd.openxmlformats-officedocument.presentationml.presentation - application/pdf content_type_identifier: custom_file_contenttype content_field_identifier: file name_field_identifier: name ibexa.multifile_upload.fallback_content_type: content_type_identifier: custom_file_contenttype content_field_identifier: file name_field_identifier: name ``` # Object states Object states are user-defined states that can be assigned to content items. They're contained in groups. *[Image: Object State group]* If a state group contains any states, each content item is automatically assigned a state from this group. You can assign states to content in the back office in the content item's **Technical details** tab. *[Image: Assigning an object state to a content item]* By default, Ibexa DXP contains one object state group: **Lock**, with states **Locked** and **Not locked**. *[Image: \*\*Lock\*\* Object state]* Object states can be used in conjunction with [permissions](https://doc.ibexa.co/en/latest/permissions/permission_overview/index.md), in particular with the [object state limitation](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#object-state-limitation). Their specific use cases depend on your needs and the setup of your permission system. # Configuration Ibexa DXP configuration is delivered by means of a number of dedicated configuration files. It contains everything from selecting the content repository to SiteAccesses to language settings. ### Configuration format The recommended configuration format is YAML. It's used by default in the kernel (and in examples throughout the documentation). However, you can also use XML or PHP formats for configuration. ### Configuration files Configuration files are located in the `config` folder. Configuration is provided per package in the `config/packages` folder, and routes are defined per package in `config/routes`. `config/packages/ibexa.yaml` contains basic configuration. It stores, among others, [SiteAccess](https://doc.ibexa.co/en/latest/multisite/multisite/index.md) information and content view config. Other configuration is provided in respective files, for example, `config/packages/ibexa_admin_ui.yaml`, `config/packages/ibexa_http_cache.yaml`. You can make configuration environment-specific by using separate folders for each environment. These files contain additional settings and point to the general (not environment-specific) configuration that is applied in other cases. > **Note: New configuration files** > > It's good practice to provide your own configuration in separate files. Any YAML files placed in the `config/packages` folder is automatically included in the system configuration. > **Tip: Tip** > > Read more about [how configuration is handled in Symfony](https://symfony.com/doc/7.4/best_practices.html#configuration). > **Caution: Special characters** > > Avoid using special characters in your configuration files. More specifically, don't use Unicode characters from the ["Other" (`C`) categories](https://en.wikipedia.org/wiki/Unicode#General_Category_property), such as control or format characters. > > Make sure your IDE displays them. > > Be careful when copy-pasting text from a word processing software or a PDF, because it might contain hidden characters like the [soft hyphen](https://en.wikipedia.org/wiki/Soft_hyphen). ### Configuration handling > **Note: Note** > > Configuration is tightly related to the [service container](https://doc.ibexa.co/en/latest/api/php_api/php_api/#service-container). To fully understand it, you must be familiar with the service container and [its configuration](https://symfony.com/doc/7.4/service_container.html#service-parameters). Basic configuration handling in Ibexa DXP is similar to what is commonly possible with Symfony. You can define key/value pairs in your configuration files. Internally and by convention, keys follow a *dot syntax*, where the different segments follow your configuration hierarchy. Keys are usually prefixed by a *namespace* corresponding to your application. All kinds of values are accepted, including arrays and deep hashes. For configuration that is meant to be exposed to an end-user (or end-developer), it's usually a good idea to also [implement semantic configuration](https://symfony.com/doc/7.4/components/config/definition.html). You can also [implement SiteAccess-aware semantic configuration](https://doc.ibexa.co/en/latest/multisite/siteaccess/siteaccess_aware_configuration/index.md). For example: ``` parameters: myapp.parameter.name: someValue myapp.boolean.param: true myapp.some.hash: foo: bar an_array: [apple, banana, pear] ``` ``` // Usage inside a controller $myParameter = $this->container->getParameter( 'myapp.parameter.name' ); ``` ## Configuration settings For specific configuration settings, see: - [Back office configuration](https://doc.ibexa.co/en/latest/administration/back_office/back_office_configuration/index.md) - [Repository configuration](https://doc.ibexa.co/en/latest/administration/configuration/repository_configuration/index.md) - [Content views](https://doc.ibexa.co/en/latest/templating/templates/template_configuration/index.md) - [Multisite configuration](https://doc.ibexa.co/en/latest/multisite/multisite_configuration/index.md) - [Image variations](https://doc.ibexa.co/en/latest/content_management/images/images/#configuring-image-variations) - [Logging and debug](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/devops/#logging-and-debug-configuration) - [Authentication](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/security/development_security/#symfony-authentication) - [Sessions](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/sessions/#configuration) - [Persistence cache](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/persistence_cache/#persistence-cache-configuration) # Dynamic configuration ## ConfigResolver Dynamic configuration is handled by a ConfigResolver. It exposes the `hasParameter()` and `getParameter()` methods. You can use them to check the different *scopes* available for a given *namespace* to find the appropriate parameter. To work with the ConfigResolver, your dynamic settings must have the following name format: `..parameter.name`. ``` parameters: # Internal configuration ibexa.site_access.config.default.content.default_ttl: 60 ibexa.site_access.config.site_group.content.default_ttl: 3600   # Here "myapp" is the namespace, followed by the SiteAccess name as the parameter scope # Parameter "my_param" will have a different value in site_group and admin_group myapp.site_group.my_param: value myapp.admin_group.my_param: another value # Defining a default value, for other SiteAccesses myapp.default.my_param: Default value ``` Inside a controller, in `site_group` SiteAccess, you can use the parameters in the following way (the same applies for `hasParameter()`): ``` $configResolver = $this->getConfigResolver();   // ibexa.site_access.config is the default namespace, so no need to specify it // The following will resolve ibexa.site_access.config..content.default_ttl // In the case of site_group, it will return 3600. // Otherwise it will return the value for ibexa.site_access.config.default.content.default_ttl (60) $locationViewSetting = $configResolver->getParameter( 'content.default_ttl' ); // For you own namespace, you need to specify it, here as "myapp" $myParamSetting = $configResolver->getParameter( 'my_param', 'myapp' ); // $myParamSetting's value will be 'value'   // You can also force the scope by naming it explicitly (here as "admin_group") $myParamSettingAdmin = $configResolver->getParameter( 'my_param', 'myapp', 'admin_group' ); // $myParamSetting's value will be 'another value' ``` > **Tip: Tip** > > To learn more about scopes, see [SiteAccess documentation](https://doc.ibexa.co/en/latest/multisite/multisite_configuration/#scope). Both `getParameter()` and `hasParameter()` can take three arguments: 1. `$paramName` - the name of the parameter 2. `$namespace` - your application namespace, `myapp` in the previous example. If null, the default namespace is used, which is `ibexa.site_access.config` by default. 3. `$scope` - a SiteAccess name. If null, the current SiteAccess is used. ## Inject ConfigResolver into services You can use the ConfigResolver in your own services whenever needed. To do this, inject the `ibexa.config.resolver` service: ``` services: App\Service: arguments: ['@ibexa.config.resolver'] ``` You can also use the [autowire feature](https://symfony.com/doc/7.4/service_container/autowiring.html), by type hinting against ConfigResolverInterface. For more information about dependency injection, see [Service container](https://doc.ibexa.co/en/latest/api/php_api/php_api/#service-container). > **Note: Note** > > Don't store the retrieved config value unless you know what you're doing. SiteAccess can change during code execution, which means you might work on the wrong value. ``` namespace App; use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface;   class Service {  /** * @var \Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface */ private $configResolver;   public function __construct( ConfigResolverInterface $configResolver ) { $this->configResolver = $configResolver; } public function someMethodThatNeedConfig() { $configValue = $this->configResolver->getParameter('my_param', 'myapp'); } } ``` # Repository configuration You can define several repositories within a single application. However, you can only use one per site. ## Repository connection ### Using default values To use the default repository connection, you don't need to specify its details: ``` ibexa: repositories: # Defining Repository with alias "main" # Default storage engine is used, with default connection # Equals to: # main: { storage: { engine: legacy, connection: } } main: ~ ``` > **Note: Legacy storage engine** > > Legacy storage engine is the default storage engine for the repository. > > It uses [Doctrine DBAL](https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/) (Database Abstraction Layer). Database settings are supplied by [DoctrineBundle](https://github.com/doctrine/DoctrineBundle). As such, you can refer to [DoctrineBundle's documentation](https://github.com/doctrine/DoctrineBundle/blob/2.7.x/Resources/doc/configuration.rst#doctrine-dbal-configuration). If no repository is specified for a SiteAccess or SiteAccess group, the first repository defined under `ibexa.repositories` is used: ``` ibexa: repositories: main: ~ system: # All members of site_group will use "main" Repository # No need to set "repository", it will take the first defined Repository by default site_group: # ... ``` #### Multisite URI matching with multi-repository setup You can use only one repository (database) per domain. This doesn't prohibit using [different repositories](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/persistence_cache/#multi-repository-setup) on different subdomains. However, when you use URI matching for multisite setup, all SiteAccesses sharing domain also need to share repository. For example: - `ibexa.co` domain can use `ibexa_repo` - `doc.ibexa.co` domain can use `doc_repo` But the following configuration would be invalid: - `ibexa.co` domain can use `ibexa_repo` - `ibexa.co/doc` **cannot** use `doc_repo`, as it's under the same domain. Invalid configuration causes problems for different parts of the system, for example, back-end UI, REST interface, and other non-SiteAccess-aware Symfony routes such as `/_fos_user_context_hash` used by [HTTP cache](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/http_cache/http_cache/index.md). ### Entity manager If you use the [Doctrine entity manager](https://www.doctrine-project.org/projects/doctrine-orm/en/2.10/tutorials/getting-started.html#obtaining-the-entitymanager), you're unable to connect different SiteAccesses to different databases. To have this possibility, you need to use the SiteAccess-aware entity manager: `ibexa.doctrine.orm.entity_manager`. To inject your entities into the SiteAccess-aware entity manager, use the following configuration: ``` ibexa: orm: entity_mappings: IbexaCoreBundle: is_bundle: true type: attribute dir: Entity prefix: Ibexa\Bundle\Core\Entity ``` For more information, see [DoctrineBundle documentation](https://symfony.com/doc/7.4/reference/configuration/doctrine.html). > **Note: Note** > > In contrast with DoctrineBundle, when you use the SiteAccess-aware entity manager you need to explicitly set all options: `dir` (it still accepts relative path in case of bundles), `prefix`, `type`, and `is_bundle`. ### Defining custom connection You can also explicitly define a custom repository connection: ``` doctrine: dbal: default_connection: my_connection_name connections: my_connection_name: driver: pdo_mysql host: localhost port: 3306 dbname: my_database user: my_user password: my_password charset: UTF8MB4 my_second_connection_name: driver: pdo_mysql url: '%env(resolve:SECOND_DATABASE_URL)%' charset: UTF8MB4 another_connection_name: # ... ibexa: repositories: first_repository: storage: engine: legacy connection: my_connection_name config: {} # Configuring search is required when using Legacy search engine search: connection: my_connection_name second_repository: storage: engine: legacy connection: my_second_connection_name config: {} search: connection: my_second_connection_name another_repository: storage: engine: legacy connection: another_connection_name config: {} search: connection: another_connection_name # ... system: my_first_siteaccess: repository: first_repository my_second_siteaccess: repository: second_repository ``` ``` # .env.local SECOND_DATABASE_URL=otherdb://otheruser:otherpasswd@otherhost:otherport/otherdbname?otherdbserversion ``` ## Field groups configuration Field groups, used in content and content type editing, can be configured under the `repositories` key. Values entered there are field group *identifiers*: ``` repositories: default: fields_groups: list: [content, features, metadata] default: content ``` These identifiers can be given human-readable values and can be translated. Those values are used when editing content types. The translation domain is `ibexa_fields_groups`. This example in `translations/ibexa_fields_groups.en.yaml` defines English names for field groups: ``` content: Content metadata: Metadata user_data: User data ``` ## Limit of archived content versions `default_version_archive_limit` controls the number of archived versions per content item that are stored in the repository. By default it's set to 5. This setting is configured in the following way (typically in `ibexa.yaml`): ``` ibexa: repositories: default: options: default_version_archive_limit: 10 ``` This limit is enforced on publishing a new version and only covers archived versions, not drafts. > **Tip: Tip** > > Don't set `default_version_archive_limit` too high. In Legacy storage engine you can see performance degradation if you store too many versions. The default value of 5 is the recommended value, but the less content you have overall, the more you can increase this to, for instance, 25 or even 50. ### Grace period for archived versions After a new version of a content item is published, the previous version, now archived, can still be loaded for a certain period of time, using the same permission set as the published version. This period is called the grace period and it prevents race conditions that can occur when a new version is published at the same time as someone is accessing the content item. The duration can be configured using the `grace_period_in_seconds` setting. After a version has been archived for longer than specified in the configuration, the grace period ends and the version is treated the same as all the other archived versions, including the need of [`content/versionread` policy](https://doc.ibexa.co/en/latest/permissions/policies/#content) to access it. ``` ibexa: repositories: default: options: grace_period_in_seconds: 30 ``` `grace_period_in_seconds` uses the [PHP's `max_execution_time`](https://www.php.net/manual/en/info.configuration.php#ini.max-execution-time) value by default. Set the value to 0 to disable grace period for archived versions. ### Removing versions on publication With `remove_archived_versions_on_publish` setting, you can control whether versions that exceed the limit are deleted when you publish a new version. ``` ibexa: repositories: default: options: remove_archived_versions_on_publish: true ``` `remove_archived_versions_on_publish` is set to `true` by default. Set it to `false` if you have multiple older versions of content and need to avoid performance drops when publishing. When you set the value to `false`, run [`ibexa:content:cleanup-versions`](#removing-old-versions) periodically to make sure that content item versions that exceed the limit are removed. ### Removing old versions You can use the `ibexa:content:cleanup-versions` command to remove old content versions. The command takes the following optional parameters: - `status` or `t` - status of versions to remove: `draft`, `archived` or `all` - `keep` or `k` - number of versions to keep - `user` or `u` - the User that the command is performed as. The user must have the `content/remove`, `content/read` and `content/versionread` policies. By default the `administrator` user is applied. - `excluded-content-types` - exclude versions of one or multiple content types from the cleanup procedure. Separate multiple content types identifiers with the comma. `ibexa:content:cleanup-versions --status --keep --user --excluded-content-types article,blog_post` For example, the following command removes archived versions as user `admin`, but leaves the 5 most recent versions: `ibexa:content:cleanup-versions --status archived --keep 5 --user administrator` ## User identifiers `ibexa_default_settings.yaml` contains two settings that indicate which content types are treated like users and user groups: ``` ibexa: system: default: user_content_type_identifier: [user] user_group_content_type_identifier: [user_group] ``` You can override these settings if you have other content types that should be treated as users/user groups in the back office. When viewing such content in the back office you're able to see, for example, the assigned policies. ## Top-level Locations You can change the default path for top-level locations such as content or media in the back office, for example: ``` ibexa: system: : subtree_paths: content: '/1/18/' media: '/1/57/' ``` ## Content Scheduler snapshots Content Scheduler snapshots speed up the rendering of Content Scheduler blocks and reduce the space used in the database. By default, five snapshots are stored, but you can modify this number with the following configuration, depending on the complexity of the Content Scheduler blocks: ``` parameters: ibexa.field_type.page.block.schedule.snapshots.amount: 10 ``` ## Repository-aware configuration In your custom development, you can create repository-aware configuration settings. This enables you to use different settings for different repositories. > **Tip: SiteAccess-aware configuration** > > If you need to use different settings per SiteAccess, not per repository, see [SiteAccess-aware configuration](https://doc.ibexa.co/en/latest/multisite/siteaccess/siteaccess_aware_configuration/index.md). To do this, create a parser that implements `Ibexa\Bundle\Core\DependencyInjection\Configuration\RepositoryConfigParserInterface`: ``` use Ibexa\Bundle\Core\DependencyInjection\Configuration\RepositoryConfigParserInterface; use Symfony\Component\Config\Definition\Builder\NodeBuilder; final class CustomRepositoryConfigParser implements RepositoryConfigParserInterface { public function addSemanticConfig(NodeBuilder $nodeBuilder): void { $nodeBuilder ->arrayNode('acme') ->children() ->scalarNode('my_setting') ->isRequired() ->defaultValue(120) ->end() ->end() ->end(); } } ``` You need to register this configuration extension in the following way: ``` final class AcmeFeatureBundle extends Bundle { public function build(ContainerBuilder $container): void { // ... /** @var Ibexa\Bundle\Core\DependencyInjection\IbexaCoreExtension $kernel */ $kernel = $container->getExtension('ibexa'); $kernel->addRepositoryConfigParser(new CustomRepositoryConfigParser()); } } ``` To access the configuration settings, use the `Ibexa\Bundle\Core\ApiLoader\RepositoryConfigurationProvider::getRepositoryConfig` method: ``` $acmeConfig = $repositoryConfigProvider->getRepositoryConfig()['acme']; ``` # Back office The back office interface is produced by the [`ibexa/admin-ui` bundle](https://github.com/ibexa/admin-ui). Additionally, it uses React-based modules that make each part of the UI extensible, and Bootstrap for styling. The interface is accessible in your browser at `http:///admin`. To extend the back office with PHP code, you can use [events](https://symfony.com/doc/7.4/event_dispatcher.html), either built-in Symfony events or events dispatched by the application. Some extensibility, such as [adding custom tags](https://doc.ibexa.co/en/latest/content_management/rich_text/extend_online_editor/#configure-custom-tags), is possible without writing your own code, with configuration and templating only. > **Note: String translations** > > Refer to [Custom string translations](https://doc.ibexa.co/en/latest/multisite/languages/back_office_translations/#custom-string-translations) to learn how to provide string translations when extending the back office. - [Back office configuration](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/administration/back_office/back_office_configuration/): Configure default upload locations, pagination limits, and more settings for the back office. - [Back office menus](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/administration/back_office/back_office_menus/back_office_menus/): All menus in the back office are based on KnpMenuBundle and you can easily extend them with new items. - [Back office tabs](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/administration/back_office/back_office_tabs/back_office_tabs/): Tabs are used for content view, in dashboard, system information and other parts of the back office and are extensible. - [Reusable components](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/administration/back_office/back_office_elements/reusable_components/): Speed up creating back office templates with the help of ready-made reusable components. - [Notifications](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/administration/back_office/notifications/): You can send notifications to users who work with the back office by using notification bars or notifications in the user menu. - [Browser](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/administration/back_office/browser/browser/): Customize the configuration of the content browser. - [Add user setting](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/administration/back_office/add_user_setting/): Add the option to select a custom preference in user menu. - [Customize calendar](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/administration/back_office/customize_calendar/): Add custom events to the calendar and customize its looks. # Back office configuration ## Pagination limits Default pagination limits for different sections of the back office can be defined through respective settings in [`ezplatform_default_settings.yaml`](https://github.com/ibexa/admin-ui/blob/main/src/bundle/Resources/config/ezplatform_default_settings.yaml#L7). You can set the pagination limit for user settings under the `ibexa.system..pagination_user` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa: system: : pagination_user: user_settings_limit: 6 ``` You can configure the following settings to manage the pagination limits for the product catalog: ``` ibexa: system: : product_catalog: pagination: attribute_definitions_limit: 10 attribute_groups_limit: 10 currencies_limit: 10 customer_groups_limit: 10 customer_group_users_limit: 10 products_limit: 10 product_types_limit: 10 product_view_custom_prices_limit: 10 regions_limit: 10 catalogs_limit: 10 ``` ## Subtree operations ### Copy subtree limit Copying large subtrees can cause performance issues, so you can limit the number of content items that can be copied at once by setting the `ibexa.system..subtree_operations.copy_subtree.limit` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files). The limit applies only to the UI of the back office and disables the "Copy subtree" operation. The default value is `100`. You can set it to `-1` for no limit, or to `0` to completely disable copying subtrees. To copy a subtree regardless of the limit, use the following console command: ``` php bin/console ibexa:copy-subtree ``` ### Query subtree limit When working with large content trees, counting child items or calculating subtree sizes can cause significant performance degradation due to unbounded database queries. You can limit these count operations by setting the `ibexa.system..subtree_operations.query_subtree.limit` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa: system: : subtree_operations: copy_subtree: limit: 100 query_subtree: limit: 500 ``` The default value for `query_subtree.limit` is `500`. You can set it to `-1` to disable the limit. This limit applies in some cases when the back office needs to determine if a location has children or calculate the number of items in a subtree. The limit does not affect the sub-items list, which still displays all child elements in a paginated way. When a limit is set, the query stops after finding the specified number of items instead of performing a full count. This significantly improves performance on locations with large numbers of children. The resulting count is displayed with a `+` sign, indicating that the result is not exact. *[Image: Example of subtree count with exceeded limit]* ## Default locations Default location IDs for [content structure, Media, and users](https://doc.ibexa.co/en/latest/content_management/locations/#top-level-locations) in the menu are configured with the `ibexa.system..location_ids` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa: system: : location_ids: content_structure: 2 media: 43 users: 5 ``` # Content tree With this configuration you can: - define configuration for a SiteAccess or a SiteAccess group - decide how many content items are displayed in the tree - set maximum depth of expanded tree - hide content types - set a tree root location - override content tree's root for specific locations ``` ibexa: system: # any SiteAccess or SiteAccess group admin_group: content_tree_module: # defines how many children are shown after expanding parent load_more_limit: 15 # users won't be able to load more children than that children_load_max_limit: 200 # maximum depth of expanded tree tree_max_depth: 10 # content types to display in content tree, value of '*' allows all CTs to be displayed allowed_content_types: '*' # content tree won't display these content types, can be used only when 'allowed_content_types' is set to '*' ignored_content_types: - post - article # ID of Location to use as tree root. If omitted - content.tree_root.location_id setting is used. tree_root_location_id: 2 # list of Location IDs for which content tree's root Location is changed contextual_tree_root_location_ids: - 2 # Home (Content structure) - 5 # Users - 43 # Media ``` # Reusable components When you extend the back office, you can use base Twig templates for commonly used UI components such as tables or tabs. The available templates are: - `@ibexadesign/ui/component/alert/alert.html.twig` - `@ibexadesign/ui/component/table/table.html.twig` - `@ibexadesign/ui/component/tab/tabs.html.twig` To use the components, [`embed`](https://twig.symfony.com/doc/3.x/tags/embed.html) them in templates. With `embed` you can override blocks that are defined inside the included template. ## Alerts The alert component has the following properties: - `type` - available types of alert: error, info, success, and warning - `icon` - name of the icon, taken from the default icon set - `icon_path` - full icon path, in case you don't want to use an icon from the default icon set - `title` - alert title - `subtitle` - displays subtitle content - `show_subtitle_below` - default set to `false`, the subtitle is displayed next to the title - `extra_content` - use to add custom elements, such as buttons or additional text - `show_close_btn` - by default set to `false`, if set to `true`, the **Close** button is displayed but requires additional JavaScript configuration on your side to work - `is_toast` - default set to `false`, applies the toast design - `class` - additional CSS classes - `attributes` - additional HTML attributes ``` {% include '@ibexadesign/ui/component/alert/alert.html.twig' with { type: 'info', title: 'Some title', subtitle: 'Some subtitle', show_subtitle_below: true, icon_path: ibexa_icon_path('visibility-hidden'), class: 'mb-4', } only %} ``` ## Details The details component consists of the following blocks: - `details_header` - `details_items` Variables: | Name | Type | Values | | --------------------- | ------ | ------------------------------------------- | | `headline` (optional) | string | if not specified, the header isn't rendered | | `headline_items` | array | | | `view_mode` | string | `vertical`, default set to `''` | | `items` | hash | {`label`, `content_raw`, `content`} | If `headline` isn't specified, the `headline_items` isn't rendered. ## Modal The modal component consists of the following blocks: ``` {% block dialog %} {% block content_before %} {% block content %} {% block header %} {% block subheader %} {% block body %} {% block footer %} {% block content_after %} ``` Variables: | Name | Type | Values | | --------------------- | ------- | ---------------------------------------------------------------- | | `size` | string | `small`, `large`, `extra-large`, default set to: `''` | | `subtitle` | string | no default value, if not defined, the `subheader` isn't rendered | | `no_header` | boolean | default set to `false` | | `no_header_border` | boolean | default set to `false` | | `class` | string | default `''` | | `id` | string | | | `has_static_backdrop` | boolean | default set to `false` | `attr` and other `attr_*` hold all HTML attributes rendered on their respective elements. `attr` | Name | Type | Values | | ---------- | ------ | ---------------- | | `class` | string | default `''` | | `role` | string | default `dialog` | | `tabindex` | string | default `-1` | `attr_dialog` | Name | Type | Values | | ------- | ------ | ------------------------- | | `class` | string | default set to `''` | | `role` | string | default set to `document` | `attr_content` | Name | Type | Values | | ------- | ------ | ------------------- | | `class` | string | default set to `''` | `attr_title` | Name | Type | Values | | ------- | ------ | ------------------- | | `class` | string | default set to `''` | `attr_close_btn` | Name | Type | Values | | ------- | ------ | ----------------------- | | `class` | string | default set to `''` | | `type` | string | default set to `button` | | `title` | string | default set to `Close` | ## Tables The table component consists of the following blocks: - `header` - headline for the table section - `headline` - table name - `actions` - action buttons, for example, create, bulk delete - `table` - the table itself - `thead` - table header content - `tbody` - table body content ### Override specific cell For the `twig` table component to have full control over rendering the rows of specific cells, only data are passed to it. Data rows are passed in an array - one row per one array element. It is necessary to put objects with the columns data in an array. There are a few types of table columns: - normal content column - `{ content: col_name }` - a column icon - `{ has_icon: true, content: col_icon }` - a checkbox column - `{ has_checkbox: true, content: col_checkbox }` - action buttons column - `{ has_action_btns: true, content: col_action_btns }` Each column has the `raw` parameter which prevents the component from the escaping content (untrusted user-generated content). If you want to create an array based on some data from the backend, create an empty array and fill it with items (which corresponds to table rows) inside for loop: ``` {% set body_rows = [] %} {% for article in pager.currentPageResults %} {# we may render checkbox using form_widget or just put HTML #} {% set col_checkbox %} {{ form_widget(form_remove.articles[article.id]) }} {% endset %} ​ {% set col_icon %} {% endset %} ``` ### Render hyperlink The following example shows how to render both text and hyperlink which redirect to the specified content. ``` {% set col_name %} {{ ibexa_content_name(article.contentInfo) }} {% endset %} {% set col_action_btns %} {% if article.userCanEdit %} {% endif %} {% endset %} {% set body_rows = body_rows|merge([{ cols: [ { has_checkbox: true, content: col_checkbox }, { has_icon: true, content: col_icon }, { content: col_name }, { content: article.contentType.name }, { has_action_btns: true, content: col_action_btns }, ]}]) %} {% endfor %} ``` ### Actions See the example below to learn how to create an action button which removes the article in the table. The table component has to be wrapped into the remove article form. As in many cases you want a button to be disabled when no item in a table is selected and enabled otherwise, there is a built-in mechanism for this. To enable it you need to add the `ibexa-toggle-btn-state` CSS class to the form element alongside `data-toggle-button-id` data-attribute which holds the id of the button that should be enabled/disabled after a checkbox state change. Next, pass a button under the `action` parameter to the table headline. Action buttons are rendered on the right side of the table headline (don't confuse it with the table header). You can also specify headline text, which is a table title displayed above, by passing it under `headline` parameter. You can generate various headline texts by using the `results_headline` macro with a few parameters: - `count` - of all results, not only displayed on the first page - `has_filters` - when using filters - `phrase` - filtering phrase - `results_headline` - ensures the headlines consistency across the platform - `head_cols` - an array for table header (not headline), corresponds with consecutive column Column types available for the table header : - normal content column `{ content: col_name }` (content is the title of the column) - icon column `{ has_icon: true }` - checkbox column `{ has_checkbox: true }` - action buttons column `{ }` Additional parameters available for all of the objects mentioned earlier: ``` - class (CSS class) - attr (HTML attributes) ``` See the example: ``` { content: 'foo', class: 'bar', attr: { colspan: 2, }, ``` - `empty_table_info_text` and `empty_table_action_text` specify texts which are displayed when the table is empty. ``` {{ form_start(form_remove, { action: path('ibexa.article.remove'), attr: { class: 'ibexa-toggle-btn-state', 'data-toggle-button-id': '#article_remove_remove' } }) }} {% include '@ibexadesign/ui/component/table/table.html.twig' with { headline: results_headline(pager.getNbResults()), head_cols: [ { has_checkbox: true }, { has_icon: true }, { content: 'article.list.name'|trans|desc('Name') }, { content: 'article.list.content_type'|trans|desc('Content type') }, { }, ], body_rows, actions: form_widget(form_remove.remove, { attr: { class: 'btn ibexa-btn ibexa-btn--ghost ibexa-btn--small', disabled: true, }}), empty_table_info_text: 'article.list.empty'|trans|desc('You have no articles yet. Your articles will show up here.'), empty_table_action_text: 'article.list.empty_desc'|trans|desc('Articles you create will show up here.'), } %} {{ form_end(form_remove) }} ``` Other table component parameters include: - `class` - (CSS table class) - `attr` - (other HTML attributes applied on the HTML table element), for example: - `attr: { 'data-some-data-attribute-you-need': 'foo' }` - `table_body_class` and `table_body_attr` are the same as mentioned earlier, but applied on the table element - `show_head_cols_if_empty` - (default: `false`), by default, when `body_rows` is empty, the table component doesn't show the table header, but you may want to have it because for example rows are rendered dynamically with JavaScript on the browser side. To avoid wrapping headline inside the form, as it's done in the earlier example, you can `embed` table and override the `between_header_and_table` block: ``` {% block between_header_and_table %} {{ form_start(form_remove, { action: path('ibexa.article.remove'), attr: { class: 'ibexa-toggle-btn-state', 'data-toggle-button-id': '#article_remove_remove' } }) }} {% endblock %} ``` This method is practical in case of another form inside headline actions or to avoid interferences with the form like button triggering its submission. By default, tables are wrapped in a scrollable wrapper which prevents them from being too long. To disable it, set the `is_scrollable` parameter to `false`. > **Tip: Tip** > > For an example of using the table component, see [Add menu item](https://doc.ibexa.co/en/latest/administration/back_office/back_office_menus/add_menu_item/index.md). ## Tabs The tab component consists of the following block: - `tab_content` - tab content The tab component supports the following variables: - `tabs` - `id` - tab ID - `label` - human-readable label for the tab - `active` - true if tab is active - `content` - HTML content of tab if `tab_content` isn't overridden - `tab_content_class` - additional CSS classes attached to `.tab-content` - `tab_content_attributes` - additional HTML attributes added to `.tab-content` ``` {% embed '@ibexadesign/ui/component/tab/tabs.html.twig' with { tabs: [ { id: 'first', label: 'First' }, { id: 'second', label: 'Second' }, ] } %} {% block tab_content %} {% embed '@ibexadesign/ui/component/tab/tab_pane.html.twig' with { id: 'first', active: true } %} {% block content %} First {% endblock %} {% endembed %} {% embed '@ibexadesign/ui/component/tab/tab_pane.html.twig' with { id: 'second' } %} {% block content %} Second.

Some Rich HTML content

{% endblock %} {% endembed %} {% endblock %} {% endembed %} ``` With tabs, you can use [`include`](https://twig.symfony.com/doc/3.x/tags/include.html) instead of `embed` when you pass tab content as a variable while rendering the template: ``` {% include '@ibexadesign/ui/component/tab/tabs.html.twig' with { tabs: [ { id: 'first', label: 'First', content: 'First tab content' }, { id: 'second', label: 'Second', content: 'Second tab content', active: true }, ] } %} ``` # Add drop-downs In Ibexa DXP, you can create a reusable custom drop-down and implement it anywhere in the back office. Follow the steps below to learn how to integrate this component to fit it to your project needs. ## Create `` input, for example: ``` {% set source %} {% endset %} ``` `` input header. | | `choices` | - | Elements listed in the drop-down. | | `preferred_choices` | Elements listed at the top of the list with a separator. | | | `value` | - | The currently selected element. It is an object with a key `value`. | | `multiple` | truefalse | Boolean. To allow users to select multiple items. | | `translation_domain` | truefalse | Used for translating choices and placeholder. | | `custom_form` | truefalse | For custom form must be set to true. | | `class` | - | Additional classes for the element with `ibexa-dropdown` class. | | `placeholder` | Placeholder displayed when no option is selected. | | | `custom_init` | truefalse | By default set to `false`. If set to `true`, requires manually initializing drop-down in JavaScript. | | `is_disabled` | truefalse | Disables drop-down. | | `is_hidden` | truefalse | Hides the whole widget. | | `is_small` | truefalse | Adjusts height of the widget (from 48px to 32px). | | `is_ghost` | truefalse | Changes layout of the widget, removes all borders and backgrounds (similar to buttons modifier). | | `min_search_items` | number, default 5 | Minimum number of options that have to be passed to show the search inside the drop-down. | | `selected_item_label` | text | Allows setting constant label for widget. By default the visible label shows the currently selected options. | | `has_select_all_toggler` | truefalse | Allows showing a "Select all" option if the minimum number of items is reached. | | `min_select_all_toggler_items` | number, default 5 | Minimum number of items the dropdown must have for the "Select all" option to appear. | *[Image: Drop-down expanded state]* ## Extend drop-down templates ### Initialize All drop-downs are searched and initialized automatically in `admin.dropdown.js`. To extend or modify the search, you need to add a `custom_init` attribute to the drop-down Twig parameters. Otherwise it's initialized two times. Next, run the following JavaScript code: ``` (function (global, document) { const container = document.querySelector('.ibexa-dropdown'); const dropdown = new global.ibexa.core.Dropdown({ container, selectorSource, }); dropdown.init(); })(window, window.document); ``` ## Configuration options Full list of options: | Name | Description | Required | | ---------------- | ----------------------------------------------------------------------------- | -------- | | `container` | Contains a reference to a DOM node where the custom drop-down is initialized. | required | | `selectorSource` | Use to change class of the source element. | required | # Custom icons ## Customize content type icons To add custom icons for existing content types or custom content types in Ibexa DXP, use the following configuration under the `ibexa.system..content_type` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa: system: default: content_type: article: thumbnail: /assets/images/custom_icon.svg#custom category: thumbnail: /bundles/ibexaadminuiassets/vendors/ids-assets/dist/img/all-icons.svg#folder ``` Place the icon in `public/assets/images` and run `yarn encore ` after adding it. > **Note: Icons format** > > To ensure proper display in the back office, all icons should have SVG format with `symbol`. Use the [scope](https://doc.ibexa.co/en/latest/multisite/multisite_configuration/#scope) if you want different icons for different SiteAccesses. To see more Admin UI's `ids-assets` icons, see [the icon reference](https://doc.ibexa.co/en/latest/templating/twig_function_reference/icon_twig_functions/#icons-reference). ### Access icons in Twig templates #### Content type icons Content type icons are accessible in Twig templates via the [`ibexa_content_type_icon()` function](https://doc.ibexa.co/en/latest/templating/twig_function_reference/icon_twig_functions/#ibexa_content_type_icon). It requires content type identifier as an argument. The function returns the path to a content type icon. ``` ``` #### UI Icons User interface icons are accessible with [`ibexa_icon_path()` function](https://doc.ibexa.co/en/latest/templating/twig_function_reference/icon_twig_functions/#ibexa_icon_path). The function returns a path from an icon identifier and an [icon set](#icon-sets) identifier arguments. ### Access icons in JavaScript Content types icons configuration is stored in a global object: `ibexa.adminUiConfig.contentTypes`. You can retrieve the icon URL with the `getContentTypeIconUrl` helper function that is set on the global `ibexa.helpers.contentType` object. It takes content type identifier as an argument and returns one of the following items: - URL of a specified content type's icon - `null` if there is no content type with specified identifier Example with `getContentTypeIconUrl`: ``` const contentTypeIconUrl = ibexa.helpers.contentType.getContentTypeIconUrl(contentTypeIdentifier); return ( ) ``` ### Icons React component You can use a React component to change icons in back office and Page Builder. The following example from the `alert.js` file shows configuration for icons in the [alert](https://doc.ibexa.co/en/latest/administration/back_office/back_office_elements/reusable_components/#alerts) component: ```
{title &&
{title}
} {subtitle &&
{subtitle}
}
{children}
{showCloseBtn && ( )}
``` `Icon` component has three attributes (called props): - `customPath` - a path to the custom icon - `name` - the path is generated inside the component provided you use icon from the system - `extraClasses` - additional CSS classes, use to set for example, icon size. ## Customize UI icons ### Icon sets You can configure icon sets to be used per SiteAccess: ``` ibexa: system: : assets: icon_sets: my_icons: /assets/images/icons/my_icons.svg additional_icons: /assets/images/icons/additional_icons.svg default_icon_set: my_icons ``` The icon sets are used by [`ibexa_icon_path()` Twig function](https://doc.ibexa.co/en/latest/templating/twig_function_reference/icon_twig_functions/#ibexa_icon_path). - If you change the `default_icon_set` from one SiteAccess to another, `ibexa_icon_path(icon)` without `set` argument targets icons from different set files - If you change the file path of an icon set from one SiteAccess to another, `ibexa_icon_path(icon, set)` with the same `set` argument targets icons from different set files The built-in default icon set is `ids-assets` (corresponding to `/bundles/ibexaadminuiassets/vendors/ids-assets/dist/img/all-icons.svg`). To see the icons available in this set, see [the icon reference](https://doc.ibexa.co/en/latest/templating/twig_function_reference/icon_twig_functions/#icons-reference). # Add drag and drop You can create a generic interface for drag and drop interactions that you can reuse in many places across the back office. First, prepare the HTML code structure and place it in a Twig template. See the example: ```
item name
item name
item name
``` To initialize a drag and drop interface, add a JavaScript Code that comes with the template following the convention: ``` (function (global, doc, ibexa) { const draggable = new ibexa.core.Draggable({ itemsContainer: doc.querySelector('.items-container-drag'), selectorItem: '.item-drag', selectorPlaceholder: '.item-placeholder-drag', }); draggable.init(); })(window, window.document, window.ibexa); ``` For more information on creating Twig templates, see [Templating basics](https://doc.ibexa.co/en/latest/templating/templates/templates/index.md). ## Configuration options Full list of options: | Option | Description | Required | | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | | `itemsContainer` | Reference to DOM node that contains a draggable item. | required | | `selectorItem` | CSS selector of a draggable item. | required | | `selectorPlaceholder` | CSS selector of a placeholder. | required | | `afterInit` | Callback function invoked after interface initialization. | optional | | `afterDragStart` | Callback function invoked after starting to drag. | optional | | `afterDragOver` | Callback function invoked after moving onto a droppable element. | optional | | `afterDrop` | Callback function invoked after dropping an element. | optional | | `attachCustomEventHandlersToItem` | Function to be invoked while attaching event handlers to every item in the item's container. Item of `HTMLElement` type is passed to the function as the first param. | optional | | `timeoutRemovePlaceholders` | The amount of time after which the not dropped item disappears.The default value is set to 500ms. | optional | # Customizing the back office with Twig Components You can customize many of the back office views by using [Twig components](https://doc.ibexa.co/en/latest/templating/components/index.md). This allows you to inject your own custom logic and extend the templates. The available groups for the back office are: ## Admin UI | Group name | Template file | | ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `admin-ui-login-form-after` | `vendor/ibexa/admin-ui/src/bundle/Resources/views/themes/admin/account/login/index.html.twig` | | `admin-ui-login-form-before` | `vendor/ibexa/admin-ui/src/bundle/Resources/views/themes/admin/account/login/index.html.twig` | | `admin-ui-content-create-form-before` | - `vendor/ibexa/admin-ui-ui/src/bundle/Resources/views/themes/admin/content/create/create.html.twig` - `vendor/ibexa/admin-ui-ui/src/bundle/Resources/views/themes/admin/ui/on_the_fly/create_on_the_fly.html.twig` - `vendor/ibexa/admin-ui-ui/src/bundle/Resources/views/themes/admin/user/create.html.twig` | | `admin-ui-content-create-form-after` | - `vendor/ibexa/admin-ui-ui/src/bundle/Resources/views/themes/admin/content/create/create.html.twig` - `vendor/ibexa/admin-ui-ui/src/bundle/Resources/views/themes/admin/ui/on_the_fly/create_on_the_fly.html.twig` - `vendor/ibexa/admin-ui-ui/src/bundle/Resources/views/themes/admin/user/create.html.twig` | | `admin-ui-content-edit-form-after` | - `vendor/ibexa/admin-ui-ui/src/bundle/Resources/views/themes/admin/content/edit/edit.html.twig` - `vendor/ibexa/admin-ui-ui/src/bundle/Resources/views/themes/admin/ui/on_the_fly/edit_on_the_fly.html.twig` - `vendor/ibexa/admin-ui-ui/src/bundle/Resources/views/themes/admin/user/edit.html.twig` | | `admin-ui-content-edit-form-before` | - `vendor/ibexa/admin-ui-ui/src/bundle/Resources/views/themes/admin/content/edit/edit.html.twig` - `vendor/ibexa/admin-ui-ui/src/bundle/Resources/views/themes/admin/ui/on_the_fly/edit_on_the_fly.html.twig` - `vendor/ibexa/admin-ui-ui/src/bundle/Resources/views/themes/admin/user/edit.html.twig` | | `admin-ui-content-edit-sections` | - `vendor/ibexa/admin-ui-ui/src/bundle/Resources/views/themes/admin/content/edit/edit.html.twig` - `vendor/ibexa/admin-ui-ui/src/bundle/Resources/views/themes/admin/ui/on_the_fly/create_on_the_fly.html.twig` - `vendor/ibexa/admin-ui-ui/src/bundle/Resources/views/themes/admin/ui/on_the_fly/edit_on_the_fly.html.twig` | | `admin-ui-content-form-create-header-actions` | `vendor/ibexa/admin-ui-ui/src/bundle/Resources/views/themes/admin/content/create/create.html.twig` | | `admin-ui-content-form-edit-header-actions` | `vendor/ibexa/admin-ui-ui/src/bundle/Resources/views/themes/admin/content/edit/edit.html.twig` | | `admin-ui-content-tree-after` | `vendor/ibexa/admin-ui/src/bundle/Resources/views/themes/admin/content/location_view.html.twig` | | `admin-ui-content-tree-before` | `vendor/ibexaadmin-ui/src/bundle/Resources/views/themes/admin/content/location_view.html.twig` | | `admin-ui-content-type-edit-sections` | `vendor/ibexa/admin-ui-ui/src/bundle/Resources/views/themes/admin/content_type/edit.html.twig` | | `admin-ui-content-type-tab-groups` | `vendor/ibexa/admin-ui-ui/src/bundle/Resources/views/themes/admin/content_type/index.html.twig` | | `admin-ui-dashboard-all-tab-groups` | `vendor/ibexa/admin-ui-ui/src/bundle/Resources/views/themes/admin/ui/dashboard/block/all.html.twig` | | `admin-ui-dashboard-blocks` | `vendor/ibexa/admin-ui-ui/src/bundle/Resources/views/themes/admin/ui/dashboard/dashboard.html.twig` | | `admin-ui-dashboard-my-tab-groups` | `vendor/ibexa/admin-ui-ui/src/bundle/Resources/views/themes/admin/ui/dashboard/block/me.html.twig` | | `admin-ui-distraction-free-mode-extras` | `vendor/ibexa/admin-ui-ui/src/bundle/Resources/views/themes/admin/ui/form_fields.html.twig` | | `admin-ui-form-content-add-translation-body` | `vendor/ibexa/admin-ui-ui/src/bundle/Resources/views/themes/admin/content/modal/add_translation.html.twig` | | `admin-ui-global-search-autocomplete-templates` | `vendor/ibexa/admin-ui-ui/src/bundle/Resources/views/themes/admin/ui/global_search.html.twig` | | `admin-ui-global-search` | `vendor/ibexa/admin-ui-ui/src/bundle/Resources/views/themes/admin/ui/layout.html.twig` | | `admin-ui-header-user-menu-middle` | `vendor/ibexa/admin-ui-ui/src/bundle/Resources/views/themes/admin/ui/menu/user.html.twig` | | `admin-ui-image-edit-actions-after` | - `vendor/ibexa/admin-ui-ui/src/bundle/Resources/views/themes/admin/ui/field_type/edit/ibexa_image.html.twig` - `vendor/ibexa/admin-ui-ui/src/bundle/Resources/views/themes/admin/ui/field_type/edit/ibexa_image_asset.html.twig` | | `admin-ui-layout-content-after` | `vendor/ibexa/admin-ui-ui/src/bundle/Resources/views/themes/admin/ui/layout.html.twig` | | `admin-ui-link-manager-block` | `vendor/ibexa/admin-ui-ui/src/bundle/Resources/views/themes/admin/url_management/url_management.html.twig` | | `admin-ui-location-view-content-alerts` | `vendor/ibexa/admin-ui/src/bundle/Resources/views/themes/admin/content/location_view.html.twig` | | `admin-ui-location-view-tab-groups` | `vendor/ibexa/admin-ui/src/bundle/Resources/views/themes/admin/content/location_view.html.twig` | | `admin-ui-location-view-tabs-after` | `vendor/ibexa/admin-ui-ui/src/bundle/Resources/views/themes/admin/ui/tab/location_view.html.twig` | | `admin-ui-script-body` | `vendor/ibexa/admin-ui-ui/src/bundle/Resources/views/themes/admin/ui/layout.html.twig` | | `admin-ui-script-head` | `vendor/ibexa/admin-ui-ui/src/bundle/Resources/views/themes/admin/ui/layout.html.twig` | | `admin-ui-stylesheet-body` | - `vendor/ibexa/admin-ui-ui/src/bundle/Resources/views/themes/admin/ui/layout_error.html.twig` - `vendor/ibexa/admin-ui-ui/src/bundle/Resources/views/themes/admin/ui/layout.html.twig` | | `admin-ui-stylesheet-head` | `vendor/ibexa/admin-ui-ui/src/bundle/Resources/views/themes/admin/ui/layout.html.twig` | | `admin-ui-systeminfo-tab-groups` | `vendor/ibexa/system-info/src/bundle/Resources/views/themes/admin/system_info/info.html.twig` | | `admin-ui-user-menu` | `vendor/ibexa/admin-ui-ui/src/bundle/Resources/views/themes/admin/ui/layout.html.twig` | | `admin-ui-user-profile-blocks` | `vendor/ibexa/admin-ui/src/bundle/Resources/views/themes/admin/account/profile/view.html.twig` | | `admin-ui-versions-table-before` | `vendor/ibexa/admin-ui/src/bundle/Resources/views/themes/admin/content/tab/versions/table.html.twig` | For more information, see [this example using few of those components](https://doc.ibexa.co/en/latest/templating/components/#example). ## Calendar | Group name | Template file | | --------------------------------- | --------------------------------------------------------------------------------------- | | `admin-ui-calendar-widget-before` | `vendor/ibexa/calendar/src/bundle/Resources/views/themes/admin/calendar/view.html.twig` | ## Site Context | Group name | Template file | | ------------------------------ | ------------------------------------------------------------------------------------------------ | | `admin-ui-content-tree-before` | `vendor/ibexa/site-context/src/bundle/Resources/views/themes/admin/content/fullscreen.html.twig` | | `admin-ui-content-tree-after` | `vendor/ibexa/site-context/src/bundle/Resources/views/themes/admin/content/fullscreen.html.twig` | ## Product Catalog | Group name | Template file | | --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | `admin-ui-attribute-definition-block` | `vendor/ibexa/product-catalog/src/bundle/Resources/views/themes/admin/product_catalog/attribute_definition/view.html.twig` | | `admin-ui-attribute-definition-options-block` | `pvendor/ibexa/roduct-catalog/src/bundle/Resources/views/themes/admin/product_catalog/attribute_definition/tab/details.html.twig` | | `admin-ui-attribute-group-block` | `vendor/ibexa/product-catalog/src/bundle/Resources/views/themes/admin/product_catalog/attribute_group/view.html.twig` | | `admin-ui-catalog-block` | `vendor/ibexa/product-catalog/src/bundle/Resources/views/themes/admin/product_catalog/catalog/view.html.twig` | | `admin-ui-customer-group-block` | `vendor/ibexa/product-catalog/src/bundle/Resources/views/themes/admin/product_catalog/customer_group/view.html.twig` | | `admin-ui-product-create-form-header-actions` | `vendor/ibexa/product-catalog/src/bundle/Resources/views/themes/admin/product_catalog/product/create.html.twig` | | `admin-ui-product-create-form-after` | `vendor/ibexa/product-catalog/src/bundle/Resources/views/themes/admin/product_catalog/product/create.html.twig` | | `admin-ui-product-edit-form-header-actions` | `vendor/ibexa/product-catalog/src/bundle/Resources/views/themes/admin/product_catalog/product/edit.html.twig` | | `admin-ui-product-edit-form-after` | `vendor/ibexa/product-catalog/src/bundle/Resources/views/themes/admin/product_catalog/product/edit.html.twig` | | `admin-ui-product-block` | `vendor/ibexa/product-catalog/src/bundle/Resources/views/themes/admin/product_catalog/product/view.html.twig` | | `admin-ui-product-translation-modal-footer` | `vendor/ibexa/product-catalog/src/bundle/Resources/views/themes/admin/product_catalog/product/modal/add_translation.html.twig` | | `admin-ui-product-translations-actions-modal` | `vendor/ibexa/product-catalog/src/bundle/Resources/views/themes/admin/product_catalog/product/tab/translations.html.twig` | | `admin-ui-product-translations-actions` | `vendor/ibexa/product-catalog/src/bundle/Resources/views/themes/admin/product_catalog/product/tab/translations.html.twig` | | `admin-ui-product-translations-row-actions` | `vendor/ibexa/product-catalog/src/bundle/Resources/views/themes/admin/product_catalog/product/tab/translations.html.twig` | | `admin-ui-product-type-block` | `vendor/ibexa/product-catalog/src/bundle/Resources/views/themes/admin/product_catalog/product_type/view.html.twig` | ## Taxonomy | Group name | Template file | | ----------------------------------- | ------------------------------------------------------------------------------------------------------------ | | `admin-ui-location-view-tab-groups` | `vendor/ibexa/taxonomy/src/bundle/Resources/views/themes/admin/ibexa/taxonomy/taxonomy_entry/show.html.twig` | ## Page Builder (Experience) | Group name | Template file | | --------------------------------- | ------------------------------------------------------------------------------------------ | | `admin-ui-infobar-options-before` | `vendor/ibexa/page-builder/src/bundle/Resources/views/page_builder/infobar/base.html.twig` | ## Order Management (Commerce) | Group name | Template file | | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | `admin-ui-order-details-summary-stats` | `vendor/ibexa/order-management/src/bundle/Resources/views/themes/admin/order_management/order/details_summary.html.twig` | | `admin-ui-order-details-summary-grid` | `vendor/ibexa/order-management/src/bundle/Resources/views/themes/admin/order_management/order/details_summary.html.twig` | ## Payments (Commerce) | Group name | Template file | | ------------------------------ | -------------------------------------------------------------------------------------------- | | `admin-ui-payment-method-tabs` | `vendor/ibexa/payment/src/bundle/Resources/views/themes/admin/payment_method/view.html.twig` | ## Shipping (Commerce) | Group name | Template file | | -------------------------------- | ------------------------------------------------------------------------------------------------------- | | `admin-ui-shipment-summary-grid` | `vendor/ibexa/shipping/src/bundle/Resources/views/themes/admin/shipment/tab/summary.html.twig` | | `admin-ui-shipping-method-block` | `vendor/ibexa/shipping/src/bundle/Resources/views/themes/admin/shipping/shipping_method/view.html.twig` | ## AI Actions | Group name | Template file | | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------- | | `admin-ui-action-configuration-tabs` | `vendor/ibexa/connector-ai/src/bundle/Resources/views/themes/admin/connector_ai/action_configuration/view.html.twig` | ## Discounts (Commerce) | Group name | Template file | | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------ | | `admin-ui-discount-block` | `vendor/ibexa/discounts/src/bundle/Resources/views/themes/admin/discounts/view.html.twig` | | `admin-ui-discount-condition-summary` | `vendor/ibexa/discounts/src/bundle/Resources/views/themes/admin/discounts/tab/details.html.twig` | | `admin-ui-discount-condition-code-usage-summary` | `vendor/ibexa/discounts/src/bundle/Resources/views/themes/admin/discounts/tab/details.html.twig` | | `admin-ui-discount-condition-code-summary` | `vendor/ibexa/discounts/src/bundle/Resources/views/themes/admin/discounts/tab/details.html.twig` | | `admin-ui-discount-condition-code-usage-limit-summary` | `vendor/ibexa/discounts/src/bundle/Resources/views/themes/admin/discounts/tab/details.html.twig` | # Formatting date and time Two methods exist that allow you to specify how the date and time should be formatted. ## With Twig filters and PHP services You can format date and time by using the following services: - `@ibexa.user.settings.short_datetime_format.formatter` - `@ibexa.user.settings.short_datet_format.formatter` - `@ibexa.user.settings.short_time_format.formatter` - `@ibexa.user.settings.full_datetime_format.formatter` - `@ibexa.user.settings.full_date_format.formatter` - `@ibexa.user.settings.full_time_format.formatter` To use them, create an `src/Service/MyService.php` file containing: ``` shortDateTimeFormatter = $shortDateTimeFormatter; // your code } public function foo() { // your code $now = new \DateTimeImmutable(); $date = $this->shortDateTimeFormatter->format($now); $utc = $this->shortDateTimeFormatter->format($now, 'UTC'); // your code } } ``` Then, add the following to `config/services.yaml`: ``` services: App\Service\MyService: arguments: $shortDateTimeFormatter: '@ibexa.user.settings.short_datetime_format.formatter' ``` ## Within User settings menu Users can set their preferred date and time formats in the user settings menu. This format is used throughout the back office. You can set the list of available formats under the `ibexa.system..user_preferences` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa: system: : user_preferences: allowed_short_date_formats: 'label for dd/MM/yyyy': 'dd/MM/yyyy' 'label for MM/dd/yyyy': 'MM/dd/yyyy' allowed_short_time_formats: 'label for HH:mm' : 'HH:mm' 'label for hh:mm a' : 'hh:mm a' allowed_full_date_formats: 'label for dd/MM/yyyy': 'dd/MM/yyyy' 'label for MM/dd/yyyy': 'MM/dd/yyyy' allowed_full_time_formats: 'label for HH:mm': 'HH:mm' 'label for hh:mm a': 'hh:mm a' ``` The default date and time format is set using: ``` ibexa: system: : user_preferences: short_datetime_format: date_format: 'dd/MM/yyyy' time_format: 'hh:mm' full_datetime_format: date_format: 'dd/MM/yyyy' time_format: 'hh:mm' ``` ## Allowed formats The following subset of the [ICU date and time formats](https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/classSimpleDateFormat.html#details) is allowed: | Symbol | Meaning | | ---------------------------------------------------------------------------- | ---------------- | | y, yy, yyyy, Y, YY, YYYY | year | | q, Q | quarter | | M, MM, MMM, MMMM, L, LL, LLL, LLLL | month | | w, WW | week | | d, dd | day of the month | | D, DDD | day of the year | | E, EE, EEE, EEEE, EEEEEE, e, ee, eee, eeee, eeeeee, c, cc, ccc, cccc, cccccc | weekday | | a | AM or PM | | h, hh, H, HH, k, kk | hour | | m, mm | minute | | s, ss, S... | second | | Z, ZZ, ZZZ, ZZZZZ | timezone | # Extending thumbnails The thumbnails API enable you to choose an image for a specific content. If you don't want to use custom thumbnails, `ContentType` is used instead. ## Thumbnail mechanism The thumbnail mechanism has two layers, and each layer can have many implementations. The mechanism checks if any of the implementations returns a field, for example, `ibexa_image`, that has function "Can be a thumbnail" turned on. *[Image: Can be a thumbnail setting]* If found, the image is used as a content type thumbnail. ### First layer First layer of the mechanism contains strategy pattern that focuses on finding a thumbnail source. The thumbnail can be found inside or outside the content type. For example for users thumbnails can be downloaded from an avatar-generating service. For this layer there are following default implementations: - The mechanism looks for fields that can be thumbnail, if found, the mechanism moves to the second layer. - If there are no fields that can be a thumbnail, the content type icon is used as a thumbnail. ### Second layer Second layer of mechanism enables selection of thumbnail from a field that the first layer has found. It searches the content type for all the fields, for example, images, with function "Can be a thumbnail" turned on. If there is more than one field in the content type that can be used as a thumbnail, this layer returns the first nonempty field as a thumbnail. This mechanism can be modified to fit your site needs, so you can decide from where and how the thumbnails is downloaded. ### Create a thumbnail mechanism First, create base strategy for returning custom thumbnails from a static file. Create `StaticStrategy.php` in `src/Strategy`. ``` $this->staticThumbnail, ]); } } ``` Next, add the strategy with the `ibexa.repository.thumbnail.strategy.content` tag and `priority: 100` to `config/services.yaml`: ``` services: App\Thumbnails\FieldValueUrl: tags: - { name: ibexa.repository.thumbnail.strategy.field, priority: 100 } ``` Priority `100` allows this strategy to be used first on a clean installation or before any other strategy with lower priority. At this point you can go to the back office and check the results. > **Note: Thumbnail mechanism** > > This strategy overrides all generated thumbnails. You can specify a specific content type. See the example [here](https://github.com/ibexa/user/blob/main/src/lib/Strategy/DefaultThumbnailStrategy.php) ## Other fields as thumbnails Any field type can generate a thumbnail, for example: - DateAndTime (`ibexa_datetime`) - you can add a mini calendar thumbnail for Appointment content type and on the day of the appointment a clock thumbnail with a specific time when it takes place - TextBlock (`ibexa_text`) - you can add a first letter of the text block that is inside ### Add ibexa_text field as thumbnail First, create a strategy that adds support for `ibexa_text` as the thumbnail. It enables you to add a thumbnail URL in the text field. Add `FieldValueUrl.php` in `src/Thumbnails`. ``` $field->value, ]); } } ``` Next, add the strategy with the `ibexa.repository.thumbnail.strategy.field` tag to `config/services.yaml`: ``` App\Thumbnails\FieldValueUrl: tags: - { name: ibexa.repository.thumbnail.strategy.field, priority: 100 } ``` At this point you can go to the back office and check the results. # Importing assets from a bundle Ibexa DXP uses [Webpack Encore](https://symfony.com/doc/7.4/frontend.html#webpack-encore) for asset management. ## Configuration from a bundle To import assets from a bundle, configure them in an `ibexa.config.js` file that you create either in the bundle's `Resources/encore/` folder, or in the `encore` folder in the root directory of your project: ``` const path = require('path'); module.exports = (Encore) => { Encore.addEntry('', [ path.resolve(__dirname, ''), ]); }; ``` Use `` to refer to this configuration entry from Twig templates: `{{ encore_entry_script_tags('', null, 'ibexa') }}` To import CSS files only, use: `{{ encore_entry_link_tags('', null, 'ibexa') }}` > **Tip: Tip** > > After you add new files, run `php bin/console cache:clear`. > > For a full example of importing asset configuration, see [`ibexa.config.js`](https://github.com/ibexa/admin-ui/blob/main/src/bundle/Resources/encore/ibexa.config.js) To edit existing configuration entries, either in the bundle's `Resources/encore/` folder, or in the `encore` folder in the root folder of your project, create an `ibexa.config.manager.js` file: ``` const path = require('path'); module.exports = (ibexaConfig, ibexaConfigManager) => { ibexaConfigManager.replace({ ibexaConfig, entryName: '', itemToReplace: path.resolve(__dirname, ''), newItem: path.resolve(__dirname, ''), }); ibexaConfigManager.remove({ ibexaConfig, entryName: '', itemsToRemove: [ path.resolve(__dirname, ''), path.resolve(__dirname, ''), ], }); ibexaConfigManager.add({ ibexaConfig, entryName: '', newItems: [ path.resolve(__dirname, ''), path.resolve(__dirname, ''), ], }); }; ``` > **Tip: Tip** > > If you don't know what `entryName` to use, you can use the browser's developer tools to check what files are loaded on the given page. Then, use the file name as `entryName`. > **Tip: Tip** > > After you add new files, run `php bin/console cache:clear`. > > For a full example of overriding configuration, see [`ibexa.config.manager.js`](https://github.com/ibexa/fieldtype-matrix/blob/main/src/bundle/Resources/encore/ibexa.config.manager.js). To add a new configuration under your own namespace and with its own dependencies, create an `ibexa.webpack.custom.config.js` file that you create either in the bundle's `Resources/encore/` folder, or in the `encore` folder in the root directory of your project, for example: ``` const Encore = require('@symfony/webpack-encore'); Encore.setOutputPath('') .setPublicPath('') .addExternals('') // ... .addEntry('', ['']); const customConfig = Encore.getWebpackConfig(); customConfig.name = 'customConfigName'; // Config or array of configs: [customConfig1, customConfig2]; module.exports = customConfig; ``` > **Tip: Tip** > > If you don't plan to add multiple entry files on the same page in your custom configuration, use the `disableSingleRuntimeChunk()` function to avoid adding a separate `runtime.js` file. Otherwise, your JS code may be run multiple times. By default, the `enableSingleRuntimeChunk()` function is used. ## Configuration from main project files If you prefer to include the asset configuration in the main project files, add it in [`webpack.config.js`](https://github.com/ibexa/recipes/blob/master/ibexa/oss/4.6/encore/webpack.config.js#L26). To overwrite the built-in assets, use the following configuration to replace, remove, or add asset files in `webpack.config.js`: ``` const ibexaConfigManager = require('./ibexa.webpack.config.manager.js'); //... ibexaConfigManager.replace({ ibexaConfig, entryName: '', itemToReplace: path.resolve(__dirname, ''), newItem: path.resolve(__dirname, ''), }); ibexaConfigManager.remove({ ibexaConfig, entryName: '', itemsToRemove: [ path.resolve(__dirname, ''), path.resolve(__dirname, ''), ], }); ibexaConfigManager.add({ ibexaConfig, entryName: '', newItems: [ path.resolve(__dirname, ''), path.resolve(__dirname, ''), ], }); ``` # Back office tabs Many elements of the back office interface, such as content view, dashboard, or system information, are built with tabs. *[Image: Tabs in System Information]* You can extend existing tab groups with new tabs, or create your own tab groups. ## Tabs A custom tab can extend one of the following classes: - [`AbstractTab`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-AdminUi-Tab-AbstractTab.html) - base tab - [`AbstractControllerBasedTab`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-AdminUi-Tab-AbstractControllerBasedTab.html) - embeds the results of a controller action in the tab - [`AbstractRouteBasedTab`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-AdminUi-Tab-AbstractRouteBasedTab.html) - embeds the results of the selected route, passing applicable parameters ``` //... class EveryoneArticleTab extends AbstractTab implements OrderedTabInterface { //... public function getIdentifier(): string { return 'everyone-article'; } public function renderView(array $parameters): string { //... return $this->twig->render('@ibexadesign/ui/dashboard/tab/all_content.html.twig', [ 'data' => $this->pagerLocationToDataMapper->map($pager, true), ]); } } ``` > **Tip: Tip** > > For a full example of creating a custom tab, see [Add dashboard tab](https://doc.ibexa.co/en/latest/administration/back_office/back_office_tabs/create_dashboard_tab/index.md). You need to register the tab as a service. Tag it with `ibexa.admin_ui.tab` and indicate the group in which it should appear: ``` services: App\Tab\Dashboard\Everyone\EveryoneArticleTab: autowire: true autoconfigure: true public: false tags: - { name: ibexa.admin_ui.tab, group: dashboard-everyone } ``` The group can be one of the existing components, or your own [custom tab group](#tab-groups). ### Tab order You can order the tabs by making the tab implement `OrderedTabInterface`. The order depends on the numerical value returned by the `getOrder` method: ``` public function getOrder(): int { return 300; } ``` Tabs are displayed according to this value in ascending order. > **Tip: Tip** > > It's a good practice to reserve some distance between these values, for example to stagger them by step of 10. It may come useful if you later need to place something between the existing tabs. You can also influence tab display (for example, order tabs, remove, or modify them) by using the following event listeners: - `TabEvents::TAB_GROUP_PRE_RENDER` - `TabEvents::TAB_PRE_RENDER` ## Tab groups You can create new tab groups by using the [`TabsComponent`](https://github.com/ibexa/admin-ui/blob/main/src/lib/Component/TabsComponent.php). To create a tab group, register it as a service: ``` services: app.my_tabs.custom_group: parent: Ibexa\AdminUi\Component\TabsComponent arguments: $groupIdentifier: 'custom_group' tags: - { name: ibexa.twig.component, group: 'admin-ui-dashboard-blocks' } ``` Tag the group with `ibexa.twig.component`. `group` indicates where the group is rendered. To learn more about this mechanism, see [Twig Components](https://doc.ibexa.co/en/latest/templating/components/index.md). And for the groups available in the back office, see [custom components in the back office](https://doc.ibexa.co/en/latest/administration/back_office/back_office_elements/custom_components/index.md). # Create dashboard tab To create a new tab in the dashboard, create an `EveryoneArticleTab.php` file in `src/Tab/Dashboard/Everyone`. This adds a tab to the **Common content** dashboard block that displays all articles in the repository. ``` sortClauses = [new SortClause\DateModified(LocationQuery::SORT_DESC)]; $query->query = new Criterion\LogicalAnd([ new Criterion\ContentTypeIdentifier('article'), ]); $pager = new Pagerfanta( new LocationSearchAdapter( $query, $this->searchService ) ); $pager->setMaxPerPage($limit); $pager->setCurrentPage($page); return $this->twig->render('@ibexadesign/ui/dashboard/tab/all_content.html.twig', [ 'data' => $this->pagerLocationToDataMapper->map($pager, true), ]); } } ``` This tab searches for content with content type "Article" (lines 50-53) and uses the built-in `all_content.html.twig` template to render the results, which ensures that the tab looks the same as the existing tabs (lines 64-66). The tab also implements [`OrderedTabInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-AdminUi-Tab-OrderedTabInterface.html) (line 17), which enables you to define the order in which the tab is displayed on the dashboard page. It's done with the `getOrder()` method (line 38). Register this tab as a service: ``` services: App\Tab\Dashboard\Everyone\EveryoneArticleTab: autowire: true autoconfigure: true public: false tags: - { name: ibexa.admin_ui.tab, group: dashboard-everyone } ``` # Tab switcher in content edit page Tabs switcher allows separating the default field types in the content type from the field types that enhance the content with new functionalities. The best example of such field types are SEO or Taxonomy, as these aren't typical field types but a field types that handle functionalities for the whole content object. The following example shows how to add a Meta tab with automatically assigned Taxonomy field type. ## Add Meta tab Before you start adding the Meta tab, make sure the content type you want to edit has [Taxonomy Entry Assignment field type](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/taxonomy/work_with_tags/#assign-tag-to-content-from-taxonomy-tree). Next, provide the semantic configuration under the `ibexa.system..admin_ui_forms` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa: system: admin_group: admin_ui_forms: content_edit: fieldtypes: ibexa_taxonomy_entry_assignment: meta: true ``` `ibexa_taxonomy_entry_assignment` - identifier for the field type `meta` - when set to `true`, puts the declared field type in the Meta tab *[Image: Meta tab]* ### Configure field groups for Meta tab The default configuration makes the `ibexa_taxonomy_entry_assignment` field always visible in the Meta tab in the content form. With this new feature, you can indicate what field types, previously set in the back office content type, are shown in the Meta tab section in the content form. You can automatically move all field types from Metadata group to the Meta tab in the content form. To do it, use the following configuration: ``` ibexa: system: admin_group: admin_ui_forms: content_edit: meta_field_groups_list: - metadata ``` *[Image: Meta tab]* To disable the feature: ``` ibexa: system: admin_group: admin_ui_forms: content_edit: meta_field_groups_list: [] ``` The `meta_field_groups_list` configuration can be overridden. ## Add custom tab First, create an event listener in the `src/EventListener/TextAnchorMenuTabListener.php`: ``` 'onAnchorMenuConfigure']; } public function onAnchorMenuConfigure(ConfigureMenuEvent $event): void { // access anchor menu root item $menu = $event->getMenu(); // if you need to access "Content" tab, use ITEM__CONTENT constant: $contentTab = $menu[ContentEditAnchorMenuBuilder::ITEM__CONTENT]; // if you need to access "Meta" tab, use ITEM__META constant: $metaTab = $menu[ContentEditAnchorMenuBuilder::ITEM__META]; // Adding new tab called "New tab" $menu->addChild('New tab', ['attributes' => ['data-target-id' => 'ibexa-edit-content-sections-new-tab']]); // Add second level item "2nd level item" to previously created "New tab" tab $menu['New tab']->addChild('2nd level item', ['attributes' => ['data-target-id' => 'ibexa-edit-content-sections-new-tab-item_2']]); } } ``` A new custom tab is defined in the line 28, the line 31 defines items for the second level. For new tabs it's also required to render its section in the content editing form. To do it, register the UI Component: ``` services: app.custom_content_edit_tab: parent: Ibexa\AdminUi\Component\TwigComponent arguments: $template: '@@ibexadesign/content_type/edit/custom_tab.html.twig' tags: - { name: ibexa.twig.component, group: 'admin-ui-content-edit-sections' } ``` Finally, create the `templates/themes/admin/content_type/edit/custom_tab.html.twig` file: ``` {% extends '@ibexadesign/ui/component/anchor_navigation/section_group.html.twig' %} {% set data_id = 'ibexa-edit-content-sections-new-tab' %} {% block sections %} {% embed '@ibexadesign/ui/component/anchor_navigation/section.html.twig' %} {% set data_id = 'ibexa-edit-content-sections-new-tab-item_2' %} {% block content %} Contents of custom secondary section {% endblock %} {% endembed %} {% endblock %} ``` # Add anchor menu to content type edit screen With the anchor menu you can increase visibility of certain [field types](https://doc.ibexa.co/en/latest/content_management/field_types/field_types/index.md), which provide more complex functionality, by separating them from the [field definitions](https://doc.ibexa.co/en/latest/administration/content_organization/content_types/#field-definitions) section in [content type](https://doc.ibexa.co/en/latest/administration/content_organization/content_types/index.md) configuration screen. One example of such field type would be [SEO](https://doc.ibexa.co/projects/userguide/en/5.0/search_engine_optimization/work_with_seo/), because it handles functionality that applies to all content items of the content type. You can use the anchor menu feature with other field types, including [custom ones](https://doc.ibexa.co/en/latest/content_management/field_types/create_custom_generic_field_type/index.md). See the following example to learn how you can add a field type as an anchor menu. ## Modify YAML configuration Modify the field type visibility under the `ibexa.system..admin_ui_forms.content_type_edit.field_types` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa: system: admin_group: admin_ui_forms: content_type_edit: field_types: : meta: true position: 100 ``` Where keys have the following meaning: - `field_type_identifier` - replace this key with an identifier of the field type that you want to make more prominent. In case of SEO, this key is `ibexa_seo`. - `meta` - when this flag is set to `true`, it separates the field type from the **Field definitions** section and puts it in an anchor menu - `position` - decides about the field type's position on the content type edit screen and in the content item, in relation to other field types Additionally, setting `meta` to `true` adds a toggle for enabling or disabling the field type. In case of SEO, it adds the **Enable SEO for this content type** toggle. Enable the toggle to display the SEO section on the content item edit page. *[Image: SEO anchor menu]* > **Note: Note** > > If you add multiple field types as anchor menus, they're automatically displayed as separate sections. # Back office menus Back office menus are based on the [KnpMenuBundle](https://github.com/KnpLabs/KnpMenuBundle) and they're extensible. > **Tip: Tip** > > For general information on how to use `MenuBuilder`, see [the official KnpMenuBundle documentation](https://symfony.com/bundles/KnpMenuBundle/current/index.html). Menus are extensible using event subscribers, for example: ``` ['onMainMenuConfigure', 0], ]; } public function onMainMenuConfigure(ConfigureMenuEvent $event): void { $menu = $event->getMenu(); $customMenuItem = $menu[MainMenuBuilder::ITEM_CONTENT]->addChild( 'main__content__custom_menu', [ 'extras' => [ 'orderNumber' => 100, ], ], ); } } ``` > **Tip: Tip** > > The event subscriber is registered as a service by default, if `autoconfigure` is enabled. If not, register it as a service and tag with `kernel.event.subscriber`. ## Menu events You can listen to the following events: | | | | ------------------------ | ------------------------------------------------------------- | | Main menu | `ConfigureMenuEvent::MAIN_MENU` | | | `ConfigureMenuEvent::USER_MENU` | | Content view | `ConfigureMenuEvent::CONTENT_SIDEBAR_RIGHT` | | | `ConfigureMenuEvent::CONTENT_EDIT_SIDEBAR_RIGHT` | | | `ConfigureMenuEvent::CONTENT_CREATE_SIDEBAR_RIGHT` | | | `ConfigureMenuEvent::CONTENT_SIDEBAR_LEFT` | | Trash | `ConfigureMenuEvent::TRASH_SIDEBAR_RIGHT` | | Section | `ConfigureMenuEvent::SECTION_EDIT_SIDEBAR_RIGHT` | | | `ConfigureMenuEvent::SECTION_CREATE_SIDEBAR_RIGHT` | | Policies and permissions | `ConfigureMenuEvent::POLICY_EDIT_SIDEBAR_RIGHT` | | | `ConfigureMenuEvent::POLICY_CREATE_SIDEBAR_RIGHT` | | | `ConfigureMenuEvent::ROLE_EDIT_SIDEBAR_RIGHT` | | | `ConfigureMenuEvent::ROLE_CREATE_SIDEBAR_RIGHT` | | | `ConfigureMenuEvent::ROLE_COPY_SIDEBAR_RIGHT` | | | `ConfigureMenuEvent::USER_EDIT_SIDEBAR_RIGHT` | | | `ConfigureMenuEvent::USER_CREATE_SIDEBAR_RIGHT` | | | `ConfigureMenuEvent::ROLE_ASSIGNMENT_CREATE_SIDEBAR_RIGHT` | | Languages | `ConfigureMenuEvent::LANGUAGE_CREATE_SIDEBAR_RIGHT` | | | `ConfigureMenuEvent::LANGUAGE_EDIT_SIDEBAR_RIGHT` | | Object states | `ConfigureMenuEvent::OBJECT_STATE_GROUP_CREATE_SIDEBAR_RIGHT` | | | `ConfigureMenuEvent::OBJECT_STATE_GROUP_EDIT_SIDEBAR_RIGHT` | | | `ConfigureMenuEvent::OBJECT_STATE_CREATE_SIDEBAR_RIGHT` | | | `ConfigureMenuEvent::OBJECT_STATE_EDIT_SIDEBAR_RIGHT` | | Content types | `ConfigureMenuEvent::CONTENT_TYPE_GROUP_CREATE_SIDEBAR_RIGHT` | | | `ConfigureMenuEvent::CONTENT_TYPE_GROUP_EDIT_SIDEBAR_RIGHT` | | | `ConfigureMenuEvent::CONTENT_TYPE_CREATE_SIDEBAR_RIGHT` | | | `ConfigureMenuEvent::CONTENT_TYPE_EDIT_SIDEBAR_RIGHT` | | | `ConfigureMenuEvent::CONTENT_TYPE_SIDEBAR_RIGHT` | | URLs and wildcards | `ConfigureMenuEvent::URL_EDIT_SIDEBAR_RIGHT` | | | `ConfigureMenuEvent::URL_WILDCARD_EDIT_SIDEBAR_RIGHT` | | User settings | `ConfigureMenuEvent::USER_PASSWORD_CHANGE_SIDEBAR_RIGHT` | | | `ConfigureMenuEvent::USER_SETTING_UPDATE_SIDEBAR_RIGHT` | ## Adding menu items To add a menu item, use the `addChild()` method. Provide the method with the new menu item's identifier and, optionally, with parameters. To add an inactive menu section, don't add a route to its parameters. The following method adds a new menu section under **Content**, and under it, a new item with custom attributes: ``` $customMenuItem->addChild( 'all_content_list', [ 'label' => 'Content List', 'route' => 'all_content_list.list', 'attributes' => [ 'class' => 'custom-menu-item', ], 'linkAttributes' => [ 'class' => 'custom-menu-item-link', ], ] ); ``` `label` is used for the new menu item in the interface. `route` is the name of the route that the menu item leads to. `attributes` adds attributes (such as CSS classes) to the container `
  • ` element of the new menu item. `linkAttributes` adds attributes to the `` element. ### Passing a parameter to a menu item You can also pass parameters to templates used to render menu items with `template_parameters`: ``` $menu->addChild( 'all_content_list', [ 'extras' => [ 'template' => '@ibexadesign/list/all_content_list.html.twig', 'template_parameters' => [ 'custom_parameter' => 'value', ], ], ] ); ``` You can then use the variable `custom_parameter` in `templates/themes/admin/list/all_content_list.html.twig`. ### Translatable labels To have translatable labels, use `translation.key` from the `messages` domain: ``` $menu->addChild( 'all_content_list', [ 'label' => 'translation.key', 'extras' => [ 'translation_domain' => 'messages', ], ] ); ``` ## Modifying menu items To modify the parameters of an existing menu item, use the `setExtra()` method. ### Custom icons You can use the `extras.icon` parameter to define an icon for a menu item. For example, the following code changes the default icon for the **Create content** button in content view: ``` $menu->getChild('main__admin') ->setExtra('icon_path', '/bundles/ibexaadminuiassets/vendors/ids-assets/dist/img/all-icons.svg#alert-error'); ``` ## Removing menu items To remove a menu item, for example, to remove the **Copy subtree** item from the right menu in content view, use the following event listener: ``` $menu->removeChild('main__bookmarks'); ``` # Add menu item To add a new menu entry in the back office, you need to use an event subscriber and subscribe to [one of the events](https://doc.ibexa.co/en/latest/administration/back_office/back_office_menus/back_office_menus/#menu-events) dispatched when building menus. The following example shows how to add a "Content list" item to the main top menu and list all content items there, with a shortcut button to edit them. ## Create event subscriber First, create an event subscriber in `src/EventSubscriber/MyMenuSubscriber.php`: ``` ['onMainMenuConfigure', 0], ]; } public function onMainMenuConfigure(ConfigureMenuEvent $event): void { $menu = $event->getMenu(); $customMenuItem = $menu[MainMenuBuilder::ITEM_CONTENT]->addChild( 'main__content__custom_menu', [ 'extras' => [ 'orderNumber' => 100, ], ], ); $customMenuItem->addChild( 'all_content_list', [ 'label' => 'Content List', 'route' => 'all_content_list.list', 'attributes' => [ 'class' => 'custom-menu-item', ], 'linkAttributes' => [ 'class' => 'custom-menu-item-link', ], ] ); } } ``` This subscriber subscribes to the `ConfigureMenuEvent::MAIN_MENU` event (see line 14). It creates a subitem with the identifier `main__content__custom_menu` (lines 22-23). Then, under this subitem, it creates an `all_content_list` menu item (lines 31-32). ## Add route Next, configure the route that the menu item leads to: ``` all_content_list.list: path: /all_content_list/{page} defaults: page: 1 _controller: App\Controller\AllContentListController::listAction ``` ## Create controller The route indicates a controller that fetches all visible content items and renders the view. Create the following controller file in `src/Controller/AllContentListController.php`: ``` query = new Criterion\Visibility(Criterion\Visibility::VISIBLE); $paginator = new Pagerfanta( new LocationSearchAdapter($query, $this->searchService) ); $paginator->setMaxPerPage(8); $paginator->setCurrentPage($page); $editForm = $this->formFactory->contentEdit(); return $this->render('@ibexadesign/all_content_list.html.twig', [ 'totalCount' => $paginator->getNbResults(), 'articles' => $paginator, 'form_edit' => $editForm, ]); } } ``` ## Add template Finally, create the `templates/themes/admin/list/all_content_list.html.twig` file indicated in line 37 in the controller: ``` {% extends '@ibexadesign/ui/layout.html.twig' %} {% block title %}{{ 'Content List'|trans }}{% endblock %} {%- block breadcrumbs -%} {% include '@ibexadesign/ui/breadcrumbs.html.twig' with { items: [ { value: 'breadcrumb.admin'|trans(domain='messages')|desc('Admin') }, { value: 'url.list'|trans|desc('Content List') } ]} %} {%- endblock -%} {%- block header -%} {% include '@ibexadesign/ui/page_title.html.twig' with { title: 'url.list'|trans|desc('Content List'), } %} {%- endblock -%} {%- block content -%}
    {% set body_rows = [] %} {% for article in articles.currentPageResults %} {% set col_edit %} {% endset %} {% set body_rows = body_rows|merge([{ cols: [ { content: article.contentInfo.name }, { content: article.contentInfo.contentType.name }, { content: article.contentInfo.modificationDate|ibexa_full_datetime }, { content: article.contentInfo.publishedDate|ibexa_full_datetime }, { content: col_edit, raw: true }, ], }]) %} {% endfor %} {% include '@ibexadesign/ui/component/table/table.html.twig' with { headline: 'Content List', head_cols: [ { content: 'Content name'|trans }, { content: 'Content type'|trans }, { content: 'Modified'|trans }, { content: 'Published'|trans }, { content: '' }, ], class: 'ibexa-table', body_rows } %} {% if articles.haveToPaginate %} {% include '@ibexadesign/ui/pagination.html.twig' with { 'pager': articles } %} {% endif %}
    {{ form_start(form_edit, { 'action': path('ibexa.content.edit'), 'attr': { 'class': 'ibexa-edit-content-form'} }) }} {{ form_widget(form_edit.language, {'attr': {'hidden': 'hidden', 'class': 'language-input'}}) }} {{ form_end(form_edit) }} {% include '@ibexadesign/content/modal/version_conflict.html.twig' %} {%- endblock -%} {% block javascripts %} {{ encore_entry_script_tags('ibexa-admin-ui-dashboard-js', null, 'ibexa') }} {%- endblock -%} ``` This template uses the [reusable table template](https://doc.ibexa.co/en/latest/administration/back_office/back_office_elements/reusable_components/#tables) to render a table that fits the style of the back office. You can configure the columns of the table in the `head_cols` variable and the regular table rows in `body_rows`. In this case, `body_rows` contains information about the content item provided by the controller, and an edit button. # Add user setting ## Create new user setting You can add new preferences to the **User Settings** menu in the back office. To do so, create a setting class implementing two interfaces: `ValueDefinitionInterface` and `FormMapperInterface`. In this example the class is located in `src/Setting/Unit.php` and enables the user to select their preference for metric or imperial unit systems. ``` 'Metric', self::IMPERIAL_OPTION => 'Imperial', default => throw new InvalidArgumentException( '$storageValue', sprintf('There is no \'%s\' option', $storageValue) ), }; } public function getDefaultValue(): string { return 'metric'; } public function mapFieldForm(FormBuilderInterface $formBuilder, ValueDefinitionInterface $value): FormBuilderInterface { $choices = [ 'Metric' => self::METRIC_OPTION, 'Imperial' => self::IMPERIAL_OPTION, ]; return $formBuilder->create( 'value', ChoiceType::class, [ 'multiple' => false, 'required' => true, 'label' => $this->getDescription(), 'choices' => $choices, ] ); } } ``` Register the setting as a service: ``` services: App\Setting\Unit: tags: - { name: ibexa.user.setting.value, identifier: unit, group: my_group, priority: 50 } - { name: ibexa.user.setting.mapper.form, identifier: unit } ``` You can order the settings in the **User** menu by setting their `priority`. `group` indicates the group that the setting is placed in. It can be one of the built-in groups, or a custom one. To create a custom setting group, create an `App/Setting/Group/MyGroup.php` file: ``` .user_settings_update_view` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa: system: admin_group: user_settings_update_view: full: unit: template: '@ibexadesign/user/setting/update_unit.html.twig' match: Identifier: [ unit ] ``` The `templates/themes/admin/user/setting/update_unit.html.twig` template must extend the `@ibexadesign/account/settings/update.html.twig` template: ``` {% extends '@ibexadesign/account/settings/update.html.twig' %} {% block form %} {{ parent() }} {% endblock %} ``` # Customize calendar By default, the Calendar displays scheduled events of the following types: - Content publication (`future_publication`) - Content hide (`future_hide`) - Block reveal (`page_block_reveal`) - Block hide (`page_block_hide`) You can perform basic actions on these events. You can also configure the calendar to display custom event types. ## Customize colors and icons You can change the color of a calendar event or change the icon of an action. The setting is SiteAccess-aware. To customize the appearance settings, use the `ibexa.system..calendar.event_types` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa: system: admin_group: calendar: event_types: future_publication: color: '#47BEDB' actions: reschedule: icon: '/bundles/ibexaadminuiassets/vendors/ids-assets/dist/img/all-icons.svg#alert-error' ``` Line 6 contains the name of the event type, either a built-in custom one. `color` defines the color in which events of this type are displayed in the calendar. `icon` is the icon used for a button with the relevant event action. *[Image: Bank holiday with custom color]* ## Configure custom events The following example shows how to create custom events which add different holidays to the calendar. First, create a new event in `src/Calendar/Holidays/Event.php`: ``` actions = new EventActionCollection($actions); } public function getTypeIdentifier(): string { return self::EVENT_TYPE_IDENTIFIER; } public function getTypeLabel(): string { return 'Holidays'; } public function getEventName(Event $event): string { return $event->getId(); } public function getActions(): EventActionCollection { return $this->actions; } } ``` You can use the identifier defined in lines 20-23 to configure [event colors](#customize-colors-and-icons). Complete the procedure by registering the new event type as a service: ``` services: App\Calendar\Holidays\EventType: arguments: $actions: [ ] tags: - { name: ibexa.calendar.event.type } ``` ## Configure event sources To add specific events to your calendar, you need to create an event source. An event source must implement `Ibexa\Contracts\Calendar\EventSource\EventSourceInterface`. One such built-in implementation is `InMemoryEventSource`. To add an in-memory collection as an event source, create `src/Calendar/Holidays/EventSourceFactory.php`: ``` createEvent('April Fools', new DateTime('2024-04-01')); $collection = new EventCollection($eventCollectionArray); return new InMemoryEventSource($collection); } private function createEvent(string $id, DateTimeInterface $dateTime): Event { return new Event($id, $dateTime, $this->eventType); } } ``` > **Note: Note** > > When creating the list of events, you must list all the `createEvent()` entities chronologically. > > For example: > > ``` > $collection = new EventCollection([ > $this->createEvent("Event 1", new DateTime("2024-01-01")), > $this->createEvent("Event 2", new DateTime("2024-01-02")), > // ... > ``` Next, register the event source as a service: ``` services: App\Calendar\Holidays\EventSourceFactory: arguments: $eventType: '@App\Calendar\Holidays\EventType' App\Calendar\Holidays\EventSource: class: Ibexa\Calendar\EventSource\InMemoryEventSource factory: [ '@App\Calendar\Holidays\EventSourceFactory', 'createEventSource' ] tags: - { name: ibexa.calendar.event.source } ``` Now you can go to the **Calendar** tab and see the configured holiday. *[Image: Custom events list view]* ### Import events from external sources You can also import events from external sources, for example, a JSON file. To do this, place the following `holidays.json` file in `src/Calendar/Holidays`: ``` [ { "date": "2024-01-01", "name": "New Year Day" }, { "date": "2023-12-25", "name": "Christmas Day" } ] ``` Next, import this file in `src/Calendar/Holidays/EventSourceFactory.php`: ``` public function createEventSource(): EventSourceInterface { $eventCollectionArray = []; $eventCollectionArray[] = $this->createEvent('April Fools', new DateTime('2024-04-01')); $items = json_decode(file_get_contents(__DIR__ . \DIRECTORY_SEPARATOR . 'holidays.json'), true); foreach ($items as $item) { $eventCollectionArray[] = $this->createEvent($item['name'], new DateTime($item['date'])); } $collection = new EventCollection($eventCollectionArray); return new InMemoryEventSource($collection); } ``` The calendar now displays the events listed in the JSON file. # Browser Browsing the content structure and selecting content from the repository uses the module Universal Discovery Widget (UDW). UDW has an interactive interface which allows you to create, move or copy content items. ## Using UDW UDW requires that you provide configuration by using the `ibexa_udw_config` Twig helper. This configuration must be spread to the props of the component itself. ``` ``` `single` configuration is one of the default configuration provided. You can also do your [own configuration](#add-new-configuration). With plain JS: ``` const container = document.querySelector('#react-udw'); const config = /* fetch the config somewhere */; //const config = JSON.parse(document.querySelector('.btn-udw-trigger).dataset.udwConfig); ReactDOM.render(React.createElement(ibexa.modules.UniversalDiscovery, { onConfirm: {Function}, onCancel: {Function}, ...config }), container); ``` With JSX: ``` const props = { onConfirm: {Function}, onCancel: {Function} }; const config = /* fetch the config somewhere */; ``` ## UDW configuration You can configure UDW under the `ibexa.system..universal_discovery_widget_module.configuration` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files). There you can set the following properties: | YMLReact props | Values | Required | Definition | | ------------------------------------------ | --------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------- | | multiple`multiple` | truefalse | no | The possibility to choose multiple locations. | | multiple_items_limit`multipleItemsLimit` | number | no | Maximum number of items with configuration `multiple: true`. | | root_location_id`rootLocationId` | number | no | UDW displays locations only below this content tree element. | | starting_location_id`startingLocationId` | number | no | This location is displayed as a starting location in UDW. | | containers_only`containersOnly` | truefalse | no | When set to `true` only containers can be selected. | | allowed_content_types`allowedContentTypes` | null[]\[`contentTypeIdentifier`\] | yes | List of allowed content types:`null` – all content types are allowed,`[]` – empty table, no content types are allowed. | | active_sort_clause`activeSortClause` | DatePublishedContentName | no | Sort Clause by which children in the content tree is sorted. | | active_sort_order`activeSortOrder` | ascendingdescending | no | Sorting order of the children in the content tree. | | active_tab`activeTab` | browsesearchbookmarks | no | Starting tab in the UDW. | | active_view`activeView` | findergridtree | no | Starting view in the UDW. | | allow_redirects`allowRedirects` | truefalse | yes | Allows to redirect content from the UDW tab to another page, for example, to content edit page. | | selected_locations`selectedLocations` | [][locationId] | no | Location that is selected automatically. | | allow_confirmation`allowConfirmation` | truefalse | yes | Shows confirmations buttons in the UDW. If set to false, it's not possible to confirm selection. | ### Content on the Fly group | YMLReact props | Values | Required | Definition | | --------------------------------------------------- | ------------------------- | -------- | --------------------------------------------------------------------------------------------------- | | allowed_languages`allowedLanguages` | null[][languageCode] | yes | Languages available in Content on the Fly:`null` - all,`[]` - none. | | allowed_locations`allowedLocations` | null[][locationId] | yes | Location under which creating content is allowed:`null` - everywhere,`[]` - nowhere. | | preselected_language`preselectedLanguage` | nulllanguageCode | yes | First language on the Content on the Fly language list:null - language order defined in the system. | | preselected_content_type`preselectedContentType` | nullcontentTypeIdentifier | yes | Content selected in Content on the Fly. | | hidden`hidden` | truefalse | yes | Content on the Fly visibility. | | auto_confirm_after_publish`autoConfirmAfterPublish` | truefalse | yes | If set to `true` UDW is automatically closed after publishing the content. | ### Tabs config group General configuration for tabs, for example, browse, search, bookmarks. | YMLReact props | Values | Required | Definition | | ---------------------------- | --------- | -------- | ----------------------------------------------------------------------------------------- | | items_per_page`itemsPerPage` | number | yes | Number of items shown on one page. | | priority`priority` | number | yes | Priority of items shown in the tab list. Item with a highest value is displayed as first. | | hidden`hidden` | truefalse | yes | Hides or reveals specific tabs. | ### Configuration available only through JS | React props | Values | Required | Definition | | ----------- | -------- | -------- | ----------------------------------------------------------------------------------------------- | | `onConfirm` | function | yes | A callback to be invoked when a user clicks the confirm button in a Universal Discovery Widget. | | `onCancel` | function | yes | A callback to be invoked when a user clicks the cancel button in a Universal Discovery Widget. | | `title` | string | yes | The title of Universal Discovery Widget. | UDW configuration is SiteAccess-aware. For each defined SiteAccess, you need to be able to use the same configuration tree to define SiteAccess-specific config. These settings need to be mapped to SiteAccess-aware internal parameters that you can retrieve with the [ConfigResolver](https://doc.ibexa.co/en/latest/administration/configuration/dynamic_configuration/#configresolver). ## Add new configuration UDW configuration can change dynamically depending on occurring events. You can use it, for example, to define which content should be exposed to a user after logging in. By default, only one element from configuration file is applied to Universal Discovery Widget. You can modify it dynamically by passing context to generate configuration based on a specific event. This context event is caught by event listener `ConfigResolveEvent::NAME` before the original configuration is used. Depending on what additional parameters are provided, original or event-specific configuration is applied. In the example below `my_custom_udw` is used as a base configuration element for the following steps: ``` ibexa: system: : universal_discovery_widget_module: configuration: my_custom_udw: multiple: false ``` ### Add new configuration to button In the `ibexa_udw_config` Twig helper, define a specific part of YAML configuration that is used to render the **Content Browser**. You can find Twig helper in your button template. In the example below, a key is pointing to `my_custom_udw` configuration and has additional parameter `johndoe`. ``` ``` ### Additional parameters If an event listener catches additional parameters passed with context, it uses a configuration specified for it in the event subscriber. In the example below, the `johndoe` parameter enables the user to choose multiple items from a **Browser window** by changing `multiple: false` from `my_custom_udw` configuration to `multiple: true`. ``` class JohnDoeCanSelectMore implements EventSubscriberInterface { private const CONFIGURATION_NAME = 'my_custom_udw'; /** * Returns an array of event names this subscriber wants to listen to. * * @return array The event names to listen to */ public static function getSubscribedEvents() { return [ ConfigResolveEvent::NAME => 'onUdwConfigResolve', ]; } /** * @param \Ibexa\AdminUi\UniversalDiscovery\Event $event */ public function onUdwConfigResolve(ConfigResolveEvent $event) { if ($event->getConfigName() !== self::CONFIGURATION_NAME) { return; } $config = $event->getConfig(); $context = $event->getContext(); if (isset($context['some_contextual_parameter'])) { if ($context['some_contextual_parameter'] === 'johndoe') { $config['multiple'] = true; } } $event->setConfig($config); } } ``` For more information, see [Symfony Doctrine Event Listeners and Subscribers tutorial](https://symfony.com/doc/7.4/event_dispatcher.html#creating-an-event-subscriber). # Add browser tab The Universal Discovery Widget (UDW) is a separate React module. By default, it contains two tabs: Browse and Bookmarks. *[Image: UDW default tabs]* Follow the instructions below to create and add a new tab called **Images** which displays all content items of the type 'Image'. ## Create tab First, in `assets/js/image-tab/`, add an `image.tab.module.js` file. ``` import React, { useContext } from 'react'; import Tab from '@ibexa-admin-ui/src/bundle/ui-dev/src/modules/universal-discovery/components/tab/tab'; import ImagesList from './components/images.list'; const ImageTabModule = () => { return (
    ); }; ``` Next, add the tab to the configuration in the same file. Each tab definition is an object containing the following properties: | Property | Value | Definition | | --------- | ------- | ------------------------------------------------------------------------------------------------------------------- | | id | string | Tab ID, for example, `image`. | | component | element | React component that represents the contents of a tab. | | label | string | Label text, for example, `Images`. | | icon | string | Path to the icon, for example, `/bundles/ibexaadminuiassets/vendors/ids-assets/dist/img/all-icons.svg#info-square`. | ``` ibexa.addConfig( 'adminUiConfig.universalDiscoveryWidget.tabs', [ { id: 'image', component: ImageTabModule, label: 'Image', icon: '/bundles/ibexaadminuiassets/vendors/ids-assets/dist/img/all-icons.svg#image', }, ], true, ); export default ImageTabModule; ``` The module governs the creation of the new tab. Complete image.tab.module.js code ``` import React, { useContext } from 'react'; import Tab from '@ibexa-admin-ui/src/bundle/ui-dev/src/modules/universal-discovery/components/tab/tab'; import ImagesList from './components/images.list'; const ImageTabModule = () => { return (
    ); }; ibexa.addConfig( 'adminUiConfig.universalDiscoveryWidget.tabs', [ { id: 'image', component: ImageTabModule, label: 'Image', icon: '/bundles/ibexaadminuiassets/vendors/ids-assets/dist/img/all-icons.svg#image', }, ], true, ); export default ImageTabModule; ``` ## Add tab to webpack config In `webpack.config.js`, add the following declaration: ``` const ibexaConfigManager = require('./ibexa.webpack.config.manager.js'); ``` Next, provide configuration for the new module: ``` ibexaConfigManager.add({ ibexaConfig, entryName: 'ibexa-admin-ui-udw-tabs-js', newItems: [path.resolve(__dirname, './assets/js/image-tab/image.tab.module.js')], }); ``` ## Provide ReactJS files Next, you need to provide a set of files used to render the module: - `images.service.js` handles fetching the images - `images.list.js` renders the image list - `image.js` renders a single image ### `images.service.js` Create a service for fetching the images by adding `images.service.js` to `assets/js/image-tab/services/`: ``` const handleRequestResponse = (response) => { if (!response.ok) { throw Error(response.statusText); } return response.json(); }; export const getImages = ({ token, siteaccess, contentId }, callback) => { const body = JSON.stringify({ ViewInput: { identifier: 'images', public: false, LocationQuery: { Criteria: {}, FacetBuilders: {}, SortClauses: {}, Filter: { ContentTypeIdCriterion: 5 }, }, }, }); const request = new Request('/api/ibexa/v2/views', { method: 'POST', headers: { Accept: 'application/vnd.ibexa.api.View+json; version=1.1', 'Content-Type': 'application/vnd.ibexa.api.ViewInput+json; version=1.1', 'X-Siteaccess': siteaccess, 'X-CSRF-Token': token, }, body, mode: 'cors', }); fetch(request) .then(handleRequestResponse) .then(callback) .catch((error) => console.log('error:load:images', error)); }; export const loadImageContent = ({ token, siteaccess, contentId }, callback) => { const body = JSON.stringify({ ViewInput: { identifier: `image-content-${contentId}`, public: false, ContentQuery: { Criteria: {}, FacetBuilders: {}, SortClauses: {}, Filter: { ContentIdCriterion: contentId }, }, }, }); const request = new Request('/api/ibexa/v2/views', { method: 'POST', headers: { Accept: 'application/vnd.ibexa.api.View+json; version=1.1', 'Content-Type': 'application/vnd.ibexa.api.ViewInput+json; version=1.1', 'X-Siteaccess': siteaccess, 'X-CSRF-Token': token, }, body, mode: 'cors', }); fetch(request) .then(handleRequestResponse) .then(callback) .catch((error) => console.log('error:load:images', error)); }; ``` ### `images.list.js` Next, create an image list by adding an `images.list.js` to `assets/js/image-tab/components/`: ``` import React, { useState, useContext, useEffect } from 'react'; import Image from './image'; import { getImages } from '../services/images.service'; import { RestInfoContext } from '@ibexa-admin-ui/src/bundle/ui-dev/src/modules/universal-discovery/universal.discovery.module'; const ImagesList = () => { const [images, setImages] = useState([]); const [page, setPage] = useState(0); const [itemsPerPage, setItemPerPage] = useState(5); const [maxPageIndex, setMaxPageIndex] = useState(0); const restInfo = useContext(RestInfoContext); const updateImagesState = (response) => { const images = response.View.Result.searchHits.searchHit.map((item) => item.value.Location); const modulo = images.length % itemsPerPage; const maxPageIndex = modulo ? (images.length - modulo) / itemsPerPage : images.length / itemsPerPage - 1; setImages(images); setMaxPageIndex(maxPageIndex); }; const showPrevPage = () => { const prevPage = page > 0 ? page - 1 : 0; setPage(prevPage); }; const showNextPage = () => { const nextPage = maxPageIndex > page ? page + 1 : maxPageIndex; setPage(nextPage); }; const renderItems = () => { const attrs = { className: 'c-images-list__items', style: { transform: `translate3d(-${page * itemsPerPage * 316}px, 0, 0)`, }, }; return (
    {images.map((imageLocation) => ( ))}
    ); }; const renderPrevBtn = () => { const attrs = { className: 'c-images-list__btn--prev', onClick: showPrevPage, }; if (page <= 0) { attrs.disabled = true; } return (
    ); }; const renderNextBtn = () => { const attrs = { className: 'c-images-list__btn--next', onClick: showNextPage, }; if (page >= maxPageIndex) { attrs.disabled = true; } return (
    ); }; useEffect(() => { getImages(restInfo, updateImagesState); }, []); return (
    {renderPrevBtn()} {renderItems()} {renderNextBtn()}
    ); }; export default ImagesList; ``` ### `image.js` Finally, create an image view by adding an `image.js` to `assets/js/image-tab/components/`: ``` import React, { useState, useEffect, useContext } from 'react'; import { loadImageContent } from '../services/images.service'; import { MarkedLocationIdContext, LoadedLocationsMapContext, MultipleConfigContext, SelectedLocationsContext, } from '@ibexa-admin-ui/src/bundle/ui-dev/src/modules/universal-discovery/universal.discovery.module'; const Image = ({ restInfo, location }) => { const [content, setContent] = useState(null); const [markedLocationId, setMarkedLocationId] = useContext(MarkedLocationIdContext); const [loadedLocationsMap, dispatchLoadedLocationsAction] = useContext(LoadedLocationsMapContext); const [selectedLocations, dispatchSelectedLocationsAction] = useContext(SelectedLocationsContext); const [multiple] = useContext(MultipleConfigContext); const updateVersionInfoState = (response) => { setContent(response.View.Result.searchHits.searchHit[0].value.Content); }; const markLocation = ({ nativeEvent }) => { const isMarkedLocationClicked = location.id === markedLocationId; if (isMarkedLocationClicked) { return; } setMarkedLocationId(location.id); dispatchLoadedLocationsAction({ type: 'CUT_LOCATIONS', locationId: location.id }); dispatchLoadedLocationsAction({ type: 'UPDATE_LOCATIONS', data: { parentLocationId: location.id, subitems: [] } }); if (!multiple) { dispatchSelectedLocationsAction({ type: 'CLEAR_SELECTED_LOCATIONS' }); dispatchSelectedLocationsAction({ type: 'ADD_SELECTED_LOCATION', location }); } }; let src = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pgo8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPgo8c3ZnIHdpZHRoPSI0MHB4IiBoZWlnaHQ9IjQwcHgiIHZpZXdCb3g9IjAgMCA0MCA0MCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWw6c3BhY2U9InByZXNlcnZlIiBzdHlsZT0iZmlsbC1ydWxlOmV2ZW5vZGQ7Y2xpcC1ydWxlOmV2ZW5vZGQ7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjEuNDE0MjE7IiB4PSIwcHgiIHk9IjBweCI+CiAgICA8ZGVmcz4KICAgICAgICA8c3R5bGUgdHlwZT0idGV4dC9jc3MiPjwhW0NEQVRBWwogICAgICAgICAgICBALXdlYmtpdC1rZXlmcmFtZXMgc3BpbiB7CiAgICAgICAgICAgICAgZnJvbSB7CiAgICAgICAgICAgICAgICAtd2Via2l0LXRyYW5zZm9ybTogcm90YXRlKDBkZWcpCiAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIHRvIHsKICAgICAgICAgICAgICAgIC13ZWJraXQtdHJhbnNmb3JtOiByb3RhdGUoLTM1OWRlZykKICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICAgICAgQGtleWZyYW1lcyBzcGluIHsKICAgICAgICAgICAgICBmcm9tIHsKICAgICAgICAgICAgICAgIHRyYW5zZm9ybTogcm90YXRlKDBkZWcpCiAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIHRvIHsKICAgICAgICAgICAgICAgIHRyYW5zZm9ybTogcm90YXRlKC0zNTlkZWcpCiAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgICAgIHN2ZyB7CiAgICAgICAgICAgICAgICAtd2Via2l0LXRyYW5zZm9ybS1vcmlnaW46IDUwJSA1MCU7CiAgICAgICAgICAgICAgICAtd2Via2l0LWFuaW1hdGlvbjogc3BpbiAxLjVzIGxpbmVhciBpbmZpbml0ZTsKICAgICAgICAgICAgICAgIC13ZWJraXQtYmFja2ZhY2UtdmlzaWJpbGl0eTogaGlkZGVuOwogICAgICAgICAgICAgICAgYW5pbWF0aW9uOiBzcGluIDEuNXMgbGluZWFyIGluZmluaXRlOwogICAgICAgICAgICB9CiAgICAgICAgXV0+PC9zdHlsZT4KICAgIDwvZGVmcz4KICAgIDxnIGlkPSJvdXRlciI+CiAgICAgICAgPGc+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik0yMCwwQzIyLjIwNTgsMCAyMy45OTM5LDEuNzg4MTMgMjMuOTkzOSwzLjk5MzlDMjMuOTkzOSw2LjE5OTY4IDIyLjIwNTgsNy45ODc4MSAyMCw3Ljk4NzgxQzE3Ljc5NDIsNy45ODc4MSAxNi4wMDYxLDYuMTk5NjggMTYuMDA2MSwzLjk5MzlDMTYuMDA2MSwxLjc4ODEzIDE3Ljc5NDIsMCAyMCwwWiIgc3R5bGU9ImZpbGw6YmxhY2s7Ii8+CiAgICAgICAgPC9nPgogICAgICAgIDxnPgogICAgICAgICAgICA8cGF0aCBkPSJNNS44NTc4Niw1Ljg1Nzg2QzcuNDE3NTgsNC4yOTgxNSA5Ljk0NjM4LDQuMjk4MTUgMTEuNTA2MSw1Ljg1Nzg2QzEzLjA2NTgsNy40MTc1OCAxMy4wNjU4LDkuOTQ2MzggMTEuNTA2MSwxMS41MDYxQzkuOTQ2MzgsMTMuMDY1OCA3LjQxNzU4LDEzLjA2NTggNS44NTc4NiwxMS41MDYxQzQuMjk4MTUsOS45NDYzOCA0LjI5ODE1LDcuNDE3NTggNS44NTc4Niw1Ljg1Nzg2WiIgc3R5bGU9ImZpbGw6cmdiKDIxMCwyMTAsMjEwKTsiLz4KICAgICAgICA8L2c+CiAgICAgICAgPGc+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik0yMCwzMi4wMTIyQzIyLjIwNTgsMzIuMDEyMiAyMy45OTM5LDMzLjgwMDMgMjMuOTkzOSwzNi4wMDYxQzIzLjk5MzksMzguMjExOSAyMi4yMDU4LDQwIDIwLDQwQzE3Ljc5NDIsNDAgMTYuMDA2MSwzOC4yMTE5IDE2LjAwNjEsMzYuMDA2MUMxNi4wMDYxLDMzLjgwMDMgMTcuNzk0MiwzMi4wMTIyIDIwLDMyLjAxMjJaIiBzdHlsZT0iZmlsbDpyZ2IoMTMwLDEzMCwxMzApOyIvPgogICAgICAgIDwvZz4KICAgICAgICA8Zz4KICAgICAgICAgICAgPHBhdGggZD0iTTI4LjQ5MzksMjguNDkzOUMzMC4wNTM2LDI2LjkzNDIgMzIuNTgyNCwyNi45MzQyIDM0LjE0MjEsMjguNDkzOUMzNS43MDE5LDMwLjA1MzYgMzUuNzAxOSwzMi41ODI0IDM0LjE0MjEsMzQuMTQyMUMzMi41ODI0LDM1LjcwMTkgMzAuMDUzNiwzNS43MDE5IDI4LjQ5MzksMzQuMTQyMUMyNi45MzQyLDMyLjU4MjQgMjYuOTM0MiwzMC4wNTM2IDI4LjQ5MzksMjguNDkzOVoiIHN0eWxlPSJmaWxsOnJnYigxMDEsMTAxLDEwMSk7Ii8+CiAgICAgICAgPC9nPgogICAgICAgIDxnPgogICAgICAgICAgICA8cGF0aCBkPSJNMy45OTM5LDE2LjAwNjFDNi4xOTk2OCwxNi4wMDYxIDcuOTg3ODEsMTcuNzk0MiA3Ljk4NzgxLDIwQzcuOTg3ODEsMjIuMjA1OCA2LjE5OTY4LDIzLjk5MzkgMy45OTM5LDIzLjk5MzlDMS43ODgxMywyMy45OTM5IDAsMjIuMjA1OCAwLDIwQzAsMTcuNzk0MiAxLjc4ODEzLDE2LjAwNjEgMy45OTM5LDE2LjAwNjFaIiBzdHlsZT0iZmlsbDpyZ2IoMTg3LDE4NywxODcpOyIvPgogICAgICAgIDwvZz4KICAgICAgICA8Zz4KICAgICAgICAgICAgPHBhdGggZD0iTTUuODU3ODYsMjguNDkzOUM3LjQxNzU4LDI2LjkzNDIgOS45NDYzOCwyNi45MzQyIDExLjUwNjEsMjguNDkzOUMxMy4wNjU4LDMwLjA1MzYgMTMuMDY1OCwzMi41ODI0IDExLjUwNjEsMzQuMTQyMUM5Ljk0NjM4LDM1LjcwMTkgNy40MTc1OCwzNS43MDE5IDUuODU3ODYsMzQuMTQyMUM0LjI5ODE1LDMyLjU4MjQgNC4yOTgxNSwzMC4wNTM2IDUuODU3ODYsMjguNDkzOVoiIHN0eWxlPSJmaWxsOnJnYigxNjQsMTY0LDE2NCk7Ii8+CiAgICAgICAgPC9nPgogICAgICAgIDxnPgogICAgICAgICAgICA8cGF0aCBkPSJNMzYuMDA2MSwxNi4wMDYxQzM4LjIxMTksMTYuMDA2MSA0MCwxNy43OTQyIDQwLDIwQzQwLDIyLjIwNTggMzguMjExOSwyMy45OTM5IDM2LjAwNjEsMjMuOTkzOUMzMy44MDAzLDIzLjk5MzkgMzIuMDEyMiwyMi4yMDU4IDMyLjAxMjIsMjBDMzIuMDEyMiwxNy43OTQyIDMzLjgwMDMsMTYuMDA2MSAzNi4wMDYxLDE2LjAwNjFaIiBzdHlsZT0iZmlsbDpyZ2IoNzQsNzQsNzQpOyIvPgogICAgICAgIDwvZz4KICAgICAgICA8Zz4KICAgICAgICAgICAgPHBhdGggZD0iTTI4LjQ5MzksNS44NTc4NkMzMC4wNTM2LDQuMjk4MTUgMzIuNTgyNCw0LjI5ODE1IDM0LjE0MjEsNS44NTc4NkMzNS43MDE5LDcuNDE3NTggMzUuNzAxOSw5Ljk0NjM4IDM0LjE0MjEsMTEuNTA2MUMzMi41ODI0LDEzLjA2NTggMzAuMDUzNiwxMy4wNjU4IDI4LjQ5MzksMTEuNTA2MUMyNi45MzQyLDkuOTQ2MzggMjYuOTM0Miw3LjQxNzU4IDI4LjQ5MzksNS44NTc4NloiIHN0eWxlPSJmaWxsOnJnYig1MCw1MCw1MCk7Ii8+CiAgICAgICAgPC9nPgogICAgPC9nPgo8L3N2Zz4K'; let alt = 'Loading meta data ...'; useEffect(() => { loadImageContent({ ...restInfo, contentId: location.ContentInfo.Content._id }, updateVersionInfoState); }, []); if (content) { const imageField = content.CurrentVersion.Version.Fields.field.find( (field) => field.fieldTypeIdentifier === 'ibexa_image', ).fieldValue; src = imageField.uri; alt = imageField.fileName; } return (
    {alt}
    ); }; export default Image; ``` ## Add styles Ensure that the new tab is styled by adding the following files to `assets/css/`. ### `images.list.css` ``` .c-images-list { display: grid; grid-template-areas: 'prev list next'; grid-template-columns: 32px 1fr 32px; grid-gap: 16px; overflow: hidden; } .c-images-list__items-wrapper { overflow: hidden; max-width: 1564px; } [class*='c-images-list__btn--'] { display: flex; align-items: center; justify-content: center; background: #f15a10; transition: background 0.2s ease-in-out, opacity 0.2s ease-in-out; } [class*='c-images-list__btn--']:focus, [class*='c-images-list__btn--']:hover { background: #ab3f0a; } [class*='c-images-list__btn--'][disabled], [class*='c-images-list__btn--'][disabled]:focus, [class*='c-images-list__btn--'][disabled]:hover { background: #f15a10; opacity: 0.5; } [class*='c-images-list__btn--'] .ibexa-icon { fill: #fff; } .c-images-list__btn--prev { grid-area: prev; } .c-images-list__btn--next { grid-area: next; } .c-images-list__items { grid-area: list; display: flex; flex-wrap: nowrap; transition: transform 0.3s ease-in-out; } .c-images-list__items .c-image { flex: 0 0 300px; } .c-images-list__items .c-image + .c-image { margin-left: 1rem; } ``` ### `image.css` ``` .c-image { width: 300px; height: 200px; background: #fff; transition: box-shadow 0.3s ease-in-out; position: relative; cursor: pointer; display: flex; } .c-image:before { content: attr(data-title); display: flex; background: rgba(0, 0, 0, 0.75); color: #fff; width: 300px; align-items: center; justify-content: center; font-weight: 700; position: absolute; top: 0; left: 0; right: 0; bottom: 0; opacity: 0; padding: 1rem; transition: opacity 0.3s ease-in-out; overflow: hidden; } .c-image:hover:before, .c-image:focus:before { opacity: 1; } .c-image__thumb { display: block; max-width: 300px; max-height: 200px; width: auto; height: auto; margin: auto; } ``` ### Add CSS to webpack Finally, add CSS in `webpack.config.js`: ``` ibexaConfigManager.add({ ibexaConfig, entryName: 'ibexa-admin-ui-layout-css', newItems: [path.resolve(__dirname, './assets/css/image.css'), path.resolve(__dirname, './assets/css/image.list.css')], }); ``` ## Check results In the back office go to **Content** -> **Dashboard**. On the top right, click the **Create content** button. In the UDW a new **Images** tab appears, listing all images from the repository. *[Image: Image tab in UDW]* > **Tip: Tip** > > If you cannot see the results or encounter an error, clear the cache and reload the application. Remember, after any change of css/js files you should always run `yarn encore dev` in the terminal. # Multi-file upload You can use the multi-file upload module in the editorial interface of Ibexa DXP. It provides an interface to publish content based on dropped files while uploading them in the interface. > **Caution: Caution** > > If you want to load the multi-file upload module, you need to load the JS code for it in your view, as it's not available by default. ## Use multi-file upload With JS only: ``` React.createElement(ibexa.modules.MultiFileUpload, { onAfterUpload: {Function}, adminUiConfig: { multiFileUpload: { defaultMappings: [{ contentTypeIdentifier: {String}, contentFieldIdentifier: {String}, contentNameIdentifier: {String}, mimeTypes: [{String}, {String}, ...] }], fallbackContentType: { contentTypeIdentifier: {String}, contentFieldIdentifier: {String}, contentNameIdentifier: {String} }, locationMappings: [{Object}], maxFileSize: {Number} }, token: {String}, siteaccess: {String} }, parentInfo: { contentTypeIdentifier: {String}, contentTypeId: {Number}, locationPath: {String}, language: {String} } }); ``` With JSX: ``` const attrs = { onAfterUpload: {Function}, adminUiConfig: { multiFileUpload: { defaultMappings: [{ contentTypeIdentifier: {String}, contentFieldIdentifier: {String}, contentNameIdentifier: {String}, mimeTypes: [{String}, {String}, ...] }], fallbackContentType: { contentTypeIdentifier: {String}, contentFieldIdentifier: {String}, contentNameIdentifier: {String} }, locationMappings: [{Object}], maxFileSize: {Number} }, token: {String}, siteaccess: {String} }, parentInfo: { contentTypeIdentifier: {String}, contentTypeId: {Number}, locationPath: {String}, language: {String} } }; ``` ## Properties list The `` module can handle additional properties. There are two types of properties: **required** and **optional**. ### Required properties All of the following properties must be used, otherwise the multi-file upload doesn't work. - **onAfterUpload** *{Function}* - a callback to be invoked immediately after a file has been uploaded - **adminUiConfig** *{Object}* - UI config object. It should keep the following structure: - **multiFileUpload** *{Object}* - multi file upload module config: - **defaultMappings** *{Array}* - a list of file type to content type mappings Sample mapping be an object and should follow the convention: - **contentTypeIdentifier** *{String}* - content type identifier - **contentFieldIdentifier** *{String}* - field identifier - **nameFieldIdentifier** *{String}* - name field identifier - **mimeTypes** *{Array}* - a list of file types assigned to a specific content type - **fallbackContentType** *{Object}* - a fallback content type definition. Should contain the following info: - **contentTypeIdentifier** *{String}* - content type identifier - **contentFieldIdentifier** *{String}* - field identifier - **nameFieldIdentifier** *{String}* - name field identifier - **locationMappings** *{Array}* - list of file type to content type mappings based on a location identifier - **maxFileSize** {Number} - maximum file size allowed for uploading. It's a number of bytes - **token** *{String}* - CSRF token - **siteaccess** *{String}* - SiteAccess identifier - **parentInfo** *{Object}* - parent location meta information: - **contentTypeIdentifier** *{String}* - content type identifier - **contentTypeId** *{Number}* - content type ID - **locationPath** *{String}* - location path string - **language** *{String}* - language code identifier ### Optional properties Optionally, the multi-file upload module can take a following list of properties: - **checkCanUpload** *{Function}* - checks whether am uploaded file can be uploaded. The callback takes four params: - **file** *{File}* - file object - **parentInfo** *{Object}* - parent location meta information - **config** *{Object}* - Multi-file Upload module config - **callbacks** *{Object}* - error callbacks list: **fileTypeNotAllowedCallback** and **fileSizeNotAllowedCallback** - **createFileStruct** *{Function}* - a function that creates a *ContentCreate* struct. The function takes two params: - **file** *{File}* - file object - **params** *{Object}* - params hash containing: **parentInfo** and **adminUiConfig** stored under the **config** key - **deleteFile** *{Function}* - a function deleting content created from a given file. It takes three params: - **systemInfo** *{Object}* - hash containing information about CSRF token and SiteAccess: **token** and **siteaccess** - **struct** *{Object}* - content struct - **callback** *{Function}* - content deleted callback - **onPopupClose** *{Function}* - function invoked when closing a Multi-file Upload popup. It takes one param: **itemsUploaded** - the list of uploaded items - **publishFile** *{Function}* - publishes an uploaded file-based content item. Takes three params: - **data** *{Object}* - an object containing information about: - **struct** *{Object}* - the ContentCreate struct () - **token** *{String}* - CSRF token - **siteaccess** *{String}* - SiteAccess identifier - **requestEventHandlers** *{Object}* - a list of upload event handlers: - **onloadstart** *{Function}* - on load start callback - **upload** *{Object}* - file upload events: - **onabort** *{Function}* - on abort callback - **onload** *{Function}* - on load callback - **onprogress** *{Function}* - on progress callback - **ontimeout** *{Function}* - on timeout callback - **callback** *{Function}* - a callback invoked when an uploaded file-based content has been published # Sub-items list The Sub-items List module is meant to be used as a part of the editorial interface of Ibexa DXP. It provides an interface for listing the sub-items of any location. ## Create custom sub-items list view You can extend the Sub-items List module to replace an existing view or add your own. The example below adds a new timeline view to highlight the modification date. *[Image: Sub-items List module using the new Timeline view]* To recreate it, start by creating the components responsible for rendering the new view. You can create two files: - `assets/js/timeline.view.component.js` responsible for rendering the whole view ``` import React from 'react'; import PropTypes from 'prop-types'; import TimelineViewItemComponent from './timeline.view.item.component'; const TimelineViewComponent = ({ items, generateLink }) => { const groupByDate = (items) => { return items.reduce((groups, item) => { const date = new Date(item.content._info.modificationDate.timestamp * 1000); const dateKey = date.toISOString().split('T')[0]; if (!groups[dateKey]) { groups[dateKey] = []; } groups[dateKey].push(item); return groups; }, {}); }; const groupedItems = groupByDate(items); return (
    {Object.entries(groupedItems).map(([date, dateItems]) => (

    {new Date(date).toLocaleDateString()}

    {dateItems.map((item) => ( ))}
    ))}
    ); }; TimelineViewComponent.propTypes = { items: PropTypes.array.isRequired, generateLink: PropTypes.func.isRequired, }; export default TimelineViewComponent; ``` - `assets/js/timeline.view.item.component.js` responsible for rendering a single item ``` import React from 'react'; import PropTypes from 'prop-types'; import Icon from '@ibexa-admin-ui-modules/common/icon/icon'; const { ibexa } = window; const TimelineViewItemComponent = ({ item, generateLink }) => { const { content } = item; const contentTypeIdentifier = content._info.contentType.identifier; const contentTypeIconUrl = ibexa.helpers.contentType.getContentTypeIconUrl(contentTypeIdentifier); const time = new Date(content._info.modificationDate.timestamp * 1000).toLocaleTimeString(); return (
    {time}
    {content._name}
    {content._info.contentType.name}
    ); }; TimelineViewItemComponent.propTypes = { item: PropTypes.object.isRequired, generateLink: PropTypes.func.isRequired, }; export default TimelineViewItemComponent; ``` Provide the necessary styling in `assets/scss/timeline.view.scss`. The example below uses Ibexa DXP's SCSS variables for consistency with the rest of the back office interface. ``` @use '@ibexa-admin-ui/src/bundle/Resources/public/scss/custom.scss' as *; .app-timeline-view { padding: calculateRem(16px); &__group { position: relative; margin-bottom: calculateRem(32px); } &__date { display: flex; align-items: center; margin-bottom: calculateRem(16px); h3 { margin: 0; font-size: $ibexa-text-font-size-large; color: $ibexa-color-dark; } } &__date-marker { width: calculateRem(12px); height: calculateRem(12px); border-radius: 50%; background: $ibexa-color-primary; margin-right: calculateRem(16px); } &__items { margin-left: calculateRem(6px); padding-left: calculateRem(32px); border-left: calculateRem(2px) solid $ibexa-color-light; } } .app-timeline-view-item { display: flex; align-items: flex-start; padding: calculateRem(16px); margin-bottom: calculateRem(8px); text-decoration: none; color: inherit; background: $ibexa-color-light-300; border-radius: $ibexa-border-radius; transition: background-color 0.2s $ibexa-admin-transition; &:hover { background: $ibexa-color-light-400; } &__time { color: $ibexa-color-dark-400; margin-right: calculateRem(16px); min-width: calculateRem(80px); } &__content { display: flex; align-items: center; } &__icon { margin-right: calculateRem(16px); } &__name { font-weight: $ibexa-font-weight-bold; margin-bottom: calculateRem(4px); } &__type { font-size: $ibexa-text-font-size-small; color: $ibexa-color-dark-400; display: flex; align-items: center; gap: calculateRem(8px); } &__type-name { line-height: calculateRem(16px); } } ``` The last step is adding the view module to the list of available views in the system, by using the provided `registerView` function. You can create a new view by providing an unique identifier, or replace an existing one by reusing its identifier. The existing view identifiers are defined as JavaScript constants in the `@ibexa-admin-ui-modules/sub-items/constants` module: - Grid view: `VIEW_MODE_GRID` constant - Table view: `VIEW_MODE_TABLE` constant Create a file called `assets/js/registerTimelineView.js`: ``` import TimelineViewComponent from './timeline.view.component.js'; import { registerView } from '@ibexa-admin-ui-modules/sub-items/services/view.registry'; // Use the existing constants to replace a view import { VIEW_MODE_GRID, VIEW_MODE_TABLE } from '@ibexa-admin-ui-modules/sub-items/constants'; registerView('timeline', { component: TimelineViewComponent, iconName: 'timeline', label: 'Timeline view', }); ``` And include it into the back office using Webpack Encore, together with your custom styles. See [configuring assets from main project files](https://doc.ibexa.co/en/latest/administration/back_office/back_office_elements/importing_assets_from_bundle/#configuration-from-main-project-files) to learn more about this mechanism. ``` const ibexaConfigManager = require('./ibexa.webpack.config.manager.js'); //... ibexaConfigManager.add({ ibexaConfig, entryName: 'ibexa-admin-ui-layout-js', newItems: [ path.resolve(__dirname, './assets/js/registerTimelineView.js') ], }); ibexaConfigManager.add({ ibexaConfig, entryName: 'ibexa-admin-ui-layout-css', newItems: [ path.resolve(__dirname, './assets/scss/timeline.view.scss'), ], }); ``` Complete the task by running `composer run post-install-cmd`. ## Use sub-items list > **Caution: Caution** > > If you want to load the Sub-items module from your custom code, you need to load the JS code for it in your view, as it's not available by default. With plain JS: ``` const containerNode = document.querySelector('#sub-items-container'); ReactDOM.render( React.createElement(ibexa.modules.SubItems, { parentLocationId: { Number }, restInfo: { token: { String }, siteaccess: { String }, }, }), containerNode, ); ``` With JSX: ``` const attrs = { parentLocationId: {Number}, restInfo: { token: {String}, siteaccess: {String} } }; ``` ## Properties list The `` module can handle additional properties. There are two types of properties: **required** and **optional**. All of them are listed below. ### Required props Without all the following properties the Sub-items module cannot work. - **parentLocationId** *{Number}* - parent location ID - **restInfo** *{Object}* - backend config object: - **token** *{String}* - CSRF token - **siteaccess** *{String}* - SiteAccess identifier - **handleEditItem** *{Function}* - callback to handle edit content action - **generateLink** *{Function}* - callback to handle view content action ### Optional properties Optionally, Sub-items module can take a following list of props: - **loadContentInfo** *{Function}* - loads content item info. Takes two params: - **contentIds** *{Array}* - list of content IDs - **callback** *{Function}* - a callback invoked when content info is loaded - **loadContentTypes** *{Function}* - loads content types. Takes one param: - **callback** *{Function}* - callback invoked when content types are loaded - **loadLocation** *{Function}* - loads location. Takes four params: - **restInfo** *{Object}* - REST info params: - **token** *{String}* - the user token - **siteaccess** *{String}* - the current SiteAccess - **queryConfig** *{Object}* - query config: - **locationId** *{Number}* - location ID - **limit** *{Number}* - content item limit - **offset** *{Number}* - items offset - **sortClauses** *{Object}* - the Sort Clauses, for example, {LocationPriority: 'ascending'} - **callback** *{Function}* - callback invoked when location is loaded - **updateLocationPriority** - updates item location priority. Takes two params: - **params** *{Object}* - parameters hash containing: - **priority** *{Number}* - priority value - **location** *{String}* - REST location ID - **token** *{String}* - CSRF token - **siteaccess** *{String}* - SiteAccess identifier - **callback** *{Function}* - callback invoked when location priority is updated - **activeView** *{String}* - active list view identifier - **extraActions** *{Array}* - list of extra actions. Each action is an object containing: - **component** *{Element}* - React component class - **attrs** *{Object}* - additional component properties - **items** *{Array}* - list of location's sub-items - **limit** *{Number}* - items limit count - **offset** *{Number}* - items limit offset - **labels** *{Object}* - list of module labels, see [sub.items.module.js](https://github.com/ibexa/admin-ui/blob/main/src/bundle/ui-dev/src/modules/sub-items/sub.items.module.js) for details. Contains definitions for sub components: - **subItems** *{Object}* - list of sub-items module labels - **tableView** *{Object}* - list of table view component labels - **tableViewItem** *{Object}* - list of table item view component labels - **loadMore** *{Object}* - list of load more component labels - **gridViewItem** *{Object}* - list of grid item view component labels - **languageContainerSelector** *{String}* - selector where the language selector should be rendered ## Reuse Sub-items list To add a Sub-items list on a page that doesn't have the (right) action sidebar, you need to do one of the following things: - add a `
    ` element with the `.ibexa-extra-actions-container` selector - change the selector in the Sub-items settings by sending the `languageContainerSelector` prop which takes the selector for the element that renders the `languageSelector`. # Notifications You can send two types on notifications to the users. [Notification bar](#notification-bars) is displayed in specific situations as a message bar appearing at the bottom of the page. It appears to whoever is doing a specific operation in the back office. *[Image: Example of an info notification]* [Custom notifications](#create-custom-notifications) are sent to a specific user. They appear in their profile in the back office. *[Image: Notification in profile]* ## Notification bars Notifications are displayed as a message bar in the back office. There are four types of notifications: `info`, `success`, `warning` and `error`. ### Displaying notifications from PHP To send a notification from PHP, inject the `TranslatableNotificationHandlerInterface` into your class. ``` $this->notificationHandler->info( /** @Desc("Notification text") */ 'example.notification.text', [], 'domain' ); ``` To have the notification translated, provide the message strings in the translation files under the correct domain and key. ### Displaying notifications from front end To create a notification from the front end (in this example, of type `info`), use the following code: ``` const eventInfo = new CustomEvent('ibexa-notify', { detail: { label: 'info', message: 'Notification text' } }); ``` Dispatch the event with `document.body.dispatchEvent(eventInfo);`. ## Create custom notifications You can send your own custom notifications to the user which are displayed in the user menu. To create a new notification you must use the `createNotification(Ibexa\Contracts\Core\Repository\Values\Notification\CreateStruct $createStruct)` method from `Ibexa\Contracts\Core\Repository\NotificationService`. Example: ``` 'onPublishVersion']; } public function onPublishVersion(PublishVersionEvent $event): void { $data = [ 'content_name' => $event->getContent()->getName(), 'content_id' => $event->getContent()->id, 'message' => 'published', ]; $notification = new CreateStruct(); $notification->ownerId = $event->getContent()->contentInfo->ownerId; $notification->type = 'ContentPublished'; $notification->data = $data; $this->notificationService->createNotification($notification); } } ``` ### Display single notification To display a single notification, write a renderer and tag it as a service. The example below presents a renderer that uses Twig to render a view: ``` twig->render('@ibexadesign/notification.html.twig', [ 'notification' => $notification, 'template_to_extend' => $templateToExtend, ]); } public function generateUrl(Notification $notification): ?string { if (array_key_exists('content_id', $notification->data)) { return $this->router->generate('ibexa.content.view', ['contentId' => $notification->data['content_id']]); } return null; } public function getTypeLabel(): string { return /** @Desc("Workflow stage changed") */ $this->translator->trans( 'workflow.notification.stage_change.label', [], 'ibexa_workflow' ); } } ``` You can add the template that is defined above in the `render()` method to one of your custom bundles: ``` {% extends template_to_extend %} {% trans_default_domain 'custom_notification' %} {% set wrapper_additional_classes = 'css-class-custom' %} {% block icon %} {% endblock %} {% block notification_type %} {{ 'Notice'|trans|desc('Notice') }} {% endblock %} {% block message %} {% embed '@ibexadesign/ui/component/table/table_body_cell.html.twig' with { class: 'ibexa-notifications-modal__description' } %} {% block content %}

    {{ notification.data.content_name }} {{ notification.data.message }}

    {% endblock %} {% endembed %} {% endblock %} ``` Finally, you need to add an entry to `config/services.yaml`: ``` services: App\Notification\MyRenderer: tags: - { name: ibexa.notification.renderer, alias: ContentPublished } ``` ### Display notification list To display a list of notifications, expand the above renderer. The example below presents a modified renderer that uses Twig to render a list view: ``` requestStack->getCurrentRequest(); if ($currentRequest && $currentRequest->attributes->getBoolean('render_all')) { $templateToExtend = '@ibexadesign/account/notifications/list_item_all.html.twig'; } return $this->twig->render('@ibexadesign/notification.html.twig', [ 'notification' => $notification, 'template_to_extend' => $templateToExtend, ]); } public function generateUrl(Notification $notification): ?string { if (array_key_exists('content_id', $notification->data)) { return $this->router->generate('ibexa.content.view', [ 'contentId' => $notification->data['content_id'], ]); } return null; } public function getTypeLabel(): string { return /** @Desc("Workflow stage changed") */ $this->translator->trans( 'workflow.notification.stage_change.label', [], 'ibexa_workflow' ); } } ``` ## Notification timeout To define the timeout for hiding Back-Office notification bars, per notification type, use the `ibexa.system..notifications..timeout` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa: system: admin: notifications: error: timeout: 0 warning: timeout: 0 success: timeout: 5000 info: timeout: 0 ``` The values shown above are the defaults. `0` means the notification doesn't hide automatically. # Integrated help Editions: LTS Update Integrated help is an [LTS Update](https://doc.ibexa.co/en/latest/ibexa_products/editions/#lts-updates) that brings documentation, training resources, and product roadmap-related information into the back office, together with user onboarding capabilities. With this feature installed, users can click the *[Image: Help icon]* icon to access relevant content straight from the UI. *[Image: Integrated help menu]* Integrated help is contextual, therefore, apart from user documentation, release notes, and partner guidelines, which are available to editors and store managers, developers can access API references, the GraphQL console, or the support portal. ## Product tours Product tours are interactive guided walkthroughs that help back office users discover Ibexa DXP features, available starting with Ibexa DXP v4.6.29. They provide step-by-step guidance directly within the application interface, accelerating user adoption and reducing training time. Developers can create custom onboarding journeys tailored to specific client implementations, user roles, or business processes. For more information, see [Product tour](https://doc.ibexa.co/en/latest/administration/back_office/product_tour/index.md). ## Install package The Integrated help LTS Update is optional. To enable it, run the following command: ``` composer require ibexa/integrated-help ``` After installation, the help center is enabled by default for all back office users. If needed, they can [disable it in user settings](https://doc.ibexa.co/projects/userguide/en/5.0/getting_started/discover_ui#disable-help-center). # Customize integrated help Editions: LTS Update The integrated help menu is part of the Integrated help introduced as an [LTS Update](https://doc.ibexa.co/en/latest/ibexa_products/editions/#lts-updates). By default, it provides editors and developers with convenient access to documentation, training and other resources directly from the back office. You can extend or modify the integrated menu in the following ways: - by disabling it for all users - by modifying a link to user documentation - by subscribing to the `ibexa_integrated_help.menu_configure.help_menu` event ## Disable integrated help functionalities After you have installed the integrated help package, you can disable the entire feature or specific functionalities on the system level. ### Disable all functionalities To disable both the Help center and the Product tour globally, for example, to run UI tests in a `dev` [environment](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/environments/index.md), in `config/packages`, create the `ibexa_integrated_help.yaml` file with the following configuration: ``` ibexa_integrated_help: enabled: false ``` ### Disable functionalities independently To disable only the Help center or only the Product tour functionalities, use the dedicated flags as in the example below: ``` ibexa_integrated_help: help_center: enabled: false # Disable only the Help center product_tour: enabled: false # Disable only the Product tour ``` ## Modify user documentation link Ibexa DXP provides a comfortable method for replacing a link to user documentation, when you do not want to modify the rest of the integrated help menu. This way you can direct application users such as editors or store managers to specific guidelines in force at your organization, without having to resort to development. To do it, in `config/packages` create the `ibexa_integrated_help.yaml` file, with the following configuration: ``` ibexa_integrated_help: user_documentation: ``` ## Intercept and modify event Ibexa DXP uses [KnpMenuBundle](https://github.com/KnpLabs/KnpMenuBundle) to build its backend menus. When it builds the integrated help menu, it dispatches the `ibexa_integrated_help.menu_configure.help_menu` event to pass information about the contents of the help menu to the front end. You can intercept this event, and change its contents by creating a subscriber. With that subscriber, you can access the `menu` object, which is an instance of the `Knp\Menu\MenuItem`, and all the options passed by this object, and modify them. This way you can adjust menu sections that are reproduced by the front end as tabs, add new items, or integrate custom links into the help system. ### Menu object structure The default `menu` object is structured as follows. Recreate this pattern when modifying an existing event with an intention to send it to the front end. ``` root (MenuItem) │ ├── help__general // ("General" section) │ ├── help__user_documentation // (User docs, highlighted menu option) │ │ (...) │ └── help__submit_idea // (Submit idea, regular option) │ └── help__developers // (conditional "Developers" section) ├── help__developer_documentation // (Developer docs, highlighted) │ (...) └── help__support_portal ``` `help_general` and `help_developers` are menu sections, or tabs. Sections consist of entries, and each entry carries the following information: - `label` - a name of the help menu item - `uri` - an external link to the resource - `isHighlighted` - a Boolean switch that decides whether the menu item should be placed at the top of the tab - `icon` - a link to a graphic file to accompany the menu item - `description` - a summary of what users can expect after clicking the menu item ### Create a subscriber Build a subscriber that intercepts the event and modifies it. In this example, it removes a product roadmap entry from the menu and adds a help menu tab with links to product videos. The tab is displayed in a production environment only. ``` 'onHelpMenuConfigure', ]; } public function onHelpMenuConfigure(ConfigureMenuEvent $event): void { $menu = $event->getMenu(); // Remove roadmap menu item if ($menu->getChild('help__general')) { $generalSection = $menu->getChild('help__general'); if ($generalSection->getChild('help__product_roadmap')) { $generalSection->removeChild('help__product_roadmap'); } } // Add videos tab, shown only in production if ($this->kernelDebug === false) { $resourcesSection = $menu->addChild('help__videos', [ 'label' => 'Product videos', ]); $resourcesSection->addChild('help__webinar_v5', [ 'label' => 'Webinar: Introducing Ibexa DXP v5', 'uri' => 'https://www.youtube.com/watch?v=qWaBHG2LRm8', 'extras' => [ 'isHighlighted' => false, 'icon' => 'https://doc.ibexa.co/en/5.0/templating/twig_function_reference/img/icons/video.svg.png', 'description' => 'Discover new features and improvements brought by Ibexa DXP v5.', ], ]); } } } ``` > **Tip: Tip** > > If `autoconfigure` is enabled, the event subscriber is registered as a service by default. If not, register it as a service and tag with `kernel.event.subscriber`. > > ``` > services: > App\EventSubscriber\HelpMenuSubscriber: > arguments: > $kernelDebug: '%kernel.debug%' > tags: > - { name: kernel.event_subscriber } > ``` For more ideas on how you can extend the help menu, see [Back office menus](https://doc.ibexa.co/en/latest/administration/back_office/back_office_menus/back_office_menus/index.md). # Product tour Editions: LTS Update Product tour is an in-app onboarding tool that helps back office contributors discover Ibexa DXP features through interactive, step-by-step guided walkthroughs. Unlike static documentation, product tours provide real-time, contextual guidance directly within the application interface. With product tours, you can create customized onboarding journeys tailored to specific client implementations, user roles, or business processes. This accelerates user adoption, reduces training time, and helps users confidently navigate the platform. Product tour functionality is available from versions 4.6.29 and 5.0.7 as part of the Integrated help package. To use product tours, you must first [install the Integrated help LTS Update](https://doc.ibexa.co/en/latest/administration/back_office/integrated_help/#install-package). ## Key concepts Product tour consists of three main elements: - **Scenario** - a complete onboarding scenario containing multiple steps that guide users through a specific feature or workflow - **Step** - an individual instruction or explanation within a scenario, containing blocks, displayed as an overlay or tooltip - **Block** - a content element within a step, such as text, images, videos, or links that provide information to the user ## Scenario types Ibexa DXP supports two types of scenarios, each designed for different use cases: ### General scenarios General tours display information in centered modals without targeting specific UI elements. These tours provide an overview of features or concepts and do not require interaction with particular interface elements. General tours are ideal for: - Introducing new users to the platform - Explaining high-level concepts or feature overviews - Welcoming users with customizable background images and branding *[Image: General scenario type]* ### Targetable scenarios Targetable scenarios highlight specific UI elements on the page and guide users through interactive workflows. Each step targets a particular element by using a CSS selector, and can draw attention to buttons, navigation elements, or other interface components. Targetable scenarios are ideal for: - Demonstrating specific features or workflows - Guiding users through multi-step processes - Teaching users how to interact with particular UI elements The steps building the scenario support three interaction modes: - **Standard** - Users navigate between steps by clicking **Previous** and **Next** buttons - **Clickable** - Users must click the highlighted element to proceed to the next step - **Draggable** - Users must drag and drop an element to continue the scenario *[Image: Targetable scenario type]* ## Scenario lifecycle Depending on scenario configuration, they automatically appear to users when they first log in or visit a specific page. Each scenario appears only once for each user. Users can complete a tour with one of the following actions: - by finishing all steps - by skipping it with the **Skip** button in general tours and **Exit tour** in targetable tours - by skipping it with the **Escape** key For **Standard** scenario steps, users can move freely between the previous and next steps. For **Clickable** and **Draggable** steps, users can't go back to the previous step without restarting the scenario and starting from the beginning. At any time, users can manually restart completed tours from their [user settings](https://doc.ibexa.co/projects/userguide/en/5.0/getting_started/get_started/#user-settings). To start building your custom onboarding scenarios, see [Configure product tour](https://doc.ibexa.co/en/latest/administration/back_office/configure_product_tour/index.md). # Configure product tour scenarios Editions: LTS Update You can configure the product tour scenarios to adapt it to your project needs, covering different onboarding scenarios. Product tour scenarios are configured with YAML configuration files. Configuration is SiteAccess-aware, allowing you to create separate onboarding experiences for different back offices in [multisite setups](https://doc.ibexa.co/en/latest/multisite/multisite/index.md). For more advanced customization cases that require PHP code, see [Customize product tour](https://doc.ibexa.co/en/latest/administration/back_office/customize_product_tour/index.md). Use the default provided configuration, available in `config/packages/ibexa_integrated_help_tours.yaml`, as a starting point that you can adjust to your needs. ## Configuration structure You configure product tour scenarios under the `ibexa.system..product_tour` key. Each scenario has a unique identifier and contains steps, which in turn contain blocks. The basic configuration structure of a scenario is as follows: ``` ibexa: system: >: # For example, admin or admin_group product_tour: : type: scenario_title_translation_key: # Optional user_groups_excluded: [, ...] # Optional steps: : # Scenario step, unique within a scenario step_title_translation_key: background_image: # Only for general type, optional target: # Only for targetable type, required interaction_mode: # Only for targetable type, optional blocks: - type: params: # Block-specific parameters # ... ``` The product tour scenarios are meant to be translatable. Ibexa recommends using translation keys instead of literal values in the YAML configuration, and providing the translations separately. Use the `ibexa_integrated_help` translation domain. For all the examples below, you can provide the translations by creating a `translations/ibexa_integrated_help.en.yaml` file with the following content: ``` tour.my_general_scenario.title: "My general scenario" title: "Welcome!" subtitle: "This is the subtitle" tour.step.description: "This is the description of the step, you can use it to explain what to do in this step." tour.link.documentation: "Documentation link" tour.list.title: "This is the list title" tour.list.item1: "First item" tour.list.item2: "Second item" tour.list.item3: "Third item" ``` To insert a line break into a translation, HTML encode the `
    ` entities to `<br/>`. ## Scenario configuration Each scenario must specify its type and can optionally restrict access by user groups. ### Scenario display order The order of scenarios in the configuration file determines the order in which they are evaluated and, if the right conditions are met, displayed. There are two [scenario types](https://doc.ibexa.co/en/latest/administration/back_office/product_tour/#scenario-types): - `general` scenarios appear at the earliest opportunity (on any page after logging in), with an exception of the user settings area - `targetable` scenarios begin if their `target` element is found in the DOM when the page is loaded. Targetable scenarios don't trigger in the user settings area as well. To control where a targetable tour appears, ensure that the first step targets an element unique to that specific page. You can target elements that appear after a user action, for example, modals like [content browser](https://doc.ibexa.co/en/latest/administration/back_office/browser/browser/index.md), but the first step's target must be present in the DOM when the page is loaded. Once a scenario ends, the system evaluates the next scenario from the configuration and, if applicable, displays it. ### Scenario title Use the optional `scenario_title_translation_key` field to provide a human-readable label for a scenario. This label is displayed in the user settings page where users can reset their product tour progress. ``` product_tour: welcome_tour: type: general scenario_title_translation_key: tour.welcome_tour.title ``` If the translation key is not set, the raw scenario identifier is used as the label. Translations must be provided in the `ibexa_integrated_help` translation domain, for example, in `translations/ibexa_integrated_help.en.yaml`. ### User group restrictions Restrict scenario visibility by excluding specific user groups by using their content remote IDs: ``` product_tour: my_scenario: user_groups_excluded: ['user_group_content_remote_id_1', 'user_group_content_remote_id_2'] # Exclude specific user groups ``` When creating new [back office user groups](https://doc.ibexa.co/en/latest/users/user_registration/#user-types), decide whether the existing product tour scenarios should be available for these new user groups. If not, add the new group to the exclusion list. > **Warning: Warning** > > If a scenario contains information meant only for specific group of users, always use the `user_groups_excluded` setting to exclude other groups. Don't rely only on UI access restrictions to control the access to scenarios, as a malicious internal user could trigger and preview them outside of the intended place. ## Step configuration Steps define individual instructions within a scenario. The configuration differs based on scenario type: ### General scenario steps General scenario steps display centered modals and support the `background_image` setting, allowing you to set a shared background image for each step. For the background, you can use an absolute URL or place your image in the `public` directory and provide the path relative to it. To resolve the path relative to the site root, [prefix it with `/`](https://developer.mozilla.org/en-US/docs/Web/API/URL_API/Resolving_relative_references#root_relative). ``` ibexa: system: admin_group: product_tour: my_general_scenario: type: 'general' scenario_title_translation_key: tour.my_general_scenario.title steps: welcome_step: step_title_translation_key: title background_image: /public/img/background.jpg blocks: - type: title params: ``` ### Targetable tour steps Targetable tour steps highlight specific UI elements by using CSS selectors. You can select a specific element by using the `target` setting. ``` ibexa: system: admin_group: product_tour: targetable_dashboard_scenario: type: 'targetable' scenario_title_translation_key: tour.targetable_dashboard_scenario.title steps: dashboard_options: step_title_translation_key: Open Dashboard options target: ".ibexa-db-header__more" # No interaction_mode specified or the value is set to null blocks: - type: text params: ``` If a step's target element doesn't exist on the page, the step isn't displayed and the scenario is stopped. Ensure your configuration matches the actual DOM structure to avoid broken scenarios. Use unique selectors to avoid triggering your scenarios on other pages. #### Interaction modes Select how the scenario step interacts with the target element by using the `interaction_mode` setting. Targetable steps support [three interaction modes](https://doc.ibexa.co/en/latest/administration/back_office/product_tour/#targetable-scenarios): > **Note: Note** > > Clickable and draggable modes are designed for single actions only (buttons, links). You can't select an entire form. If the interaction with the highlighted element results in redirection to a new page or opening a modal window where the previous target element can't be found, the "Previous" navigation button won't be displayed. **Standard mode**: The default value. A tooltip attached to a specific element on the page is displayed. Users continue the scenario with **Previous**/**Next** buttons: ``` dashboard_options: step_title_translation_key: Open Dashboard options target: ".ibexa-db-header__more" # No interaction_mode specified or the value is set to null blocks: - type: text params: text_translation_key: Learn how to customize the blocks displayed on your dashboard ``` *[Image: Standard interaction mode]* **Clickable mode**: A tooltip attached to a specific element on the page is displayed. Users continue the scenario by clicking the highlighted element. ``` open_dashboard_options: step_title_translation_key: Open Dashboard options target: '.ibexa-db-header__more' interaction_mode: clickable blocks: - type: text params: text_translation_key: Click here to customize your dashboard ``` *[Image: Clickable interaction mode]* **Draggable mode**: A tooltip attached to a specific element on the page is displayed. Users continue the scenario by [dragging](https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API#draggable_items) the highlighted element. ``` drag_and_drop_step: step_title_translation_key: Drag-and-drop blocks target: ".c-pb-toolbox-blocks-group__blocks > * .c-pb-toolbox-block__content:first-of-type" interaction_mode: draggable blocks: - type: text params: text_translation_key: Drag-and-drop blocks from the sidebar to the dashboard to customize it ``` You can use this mode only with HTML elements that have the [`draggable` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/draggable) set to `true`. *[Image: Draggable interaction mode]* ## Block types Blocks are content elements that make up each step, available both for `general` and `targetable` scenarios. Seven block types are available for building step content, and a scenario step must contain at least one. If multiple blocks are defined for a step, they are displayed one after the other. ### Title block Display bold, prominent titles: ``` - type: title params: text_translation_key: subtitle ``` ### Text block Display regular text content: ``` - type: text params: text_translation_key: tour.step.description ``` ### Link block Add external or internal links: ``` - type: link params: url: https://doc.ibexa.co text_translation_key: tour.link.documentation ``` ### List block Create bulleted lists with title: ``` - type: list params: title_translation_key: tour.list.title items_translation_keys: - tour.list.item1 - tour.list.item2 - tour.list.item3 ``` The `title_translation_key` property is optional. ### Media blocks To provide data to the media block, provide absolute URLs or place your image or video files in the `public` directory and provide the path relative to it. To resolve the path relative to the site root, [prefix it with `/`](https://developer.mozilla.org/en-US/docs/Web/API/URL_API/Resolving_relative_references#root_relative). #### Image block Embed images inside the step. You can provide alternative text by using the `alt_translation_key` property. Assuming a `public/img/diagram.jpg` image exists, set the configuration value to `/img/diagram.jpg`. ``` - type: image params: src: /public/img/diagram.jpg alt_translation_key: tour.image.alt ``` #### Video block Embed video content by using the [`video` HTML element](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/video): ``` - type: video params: # 'Big Buck Bunny' licensed under CC 3.0 by the Blender foundation. Hosted by archive.org url: https://archive.org/download/BigBuckBunny_124/Content/big_buck_bunny_720p_surround.mp4 ``` ### Custom Twig template block For advanced content, use custom Twig templates that allows you to fully control the styling of the block: ``` - type: twig_template params: template: custom_template.html.twig ``` Create the dedicated template, for example in `templates/custom_template.html.twig`. ``` {% trans_default_domain 'app' %} {{ 'custom_step_description'|trans }} ``` and provide the required translations in `translations/app.en.yaml`: ``` custom_step_description: "This is a description coming from a custom template." ``` ## Configuration examples ### Example 1: General welcome tour The following example showcases all the built-in block types for a `general` scenario consisting of a single step. ``` ibexa: system: admin_group: product_tour: my_general_scenario: type: 'general' scenario_title_translation_key: tour.my_general_scenario.title steps: welcome_step: step_title_translation_key: title background_image: /public/img/background.jpg blocks: - type: title params: text_translation_key: subtitle - type: text params: text_translation_key: tour.step.description - type: link params: url: https://doc.ibexa.co text_translation_key: tour.link.documentation - type: image params: src: /public/img/diagram.jpg alt_translation_key: tour.image.alt - type: video params: # 'Big Buck Bunny' licensed under CC 3.0 by the Blender foundation. Hosted by archive.org url: https://archive.org/download/BigBuckBunny_124/Content/big_buck_bunny_720p_surround.mp4 - type: list params: title_translation_key: tour.list.title items_translation_keys: - tour.list.item1 - tour.list.item2 - tour.list.item3 - type: twig_template params: template: custom_template.html.twig ``` ### Example 2: Targetable feature tour with interactive steps The following example showcases how the three interaction modes of a `targetable` scenario can be used to build an onboarding tour for the [customizable dashboard](https://doc.ibexa.co/en/latest/administration/dashboard/customize_dashboard/index.md): ``` ibexa: system: admin_group: product_tour: targetable_dashboard_scenario: type: 'targetable' scenario_title_translation_key: tour.targetable_dashboard_scenario.title steps: dashboard_options: step_title_translation_key: Open Dashboard options target: ".ibexa-db-header__more" # No interaction_mode specified or the value is set to null blocks: - type: text params: text_translation_key: Learn how to customize the blocks displayed on your dashboard open_dashboard_options: step_title_translation_key: Open Dashboard options target: '.ibexa-db-header__more' interaction_mode: clickable blocks: - type: text params: text_translation_key: Click here to customize your dashboard customize_dashboard: step_title_translation_key: Customize Dashboard target: '.ibexa-db-actions-popup-menu' interaction_mode: clickable blocks: - type: text params: text_translation_key: Choose "Customize dashboard" drag_and_drop_step: step_title_translation_key: Drag-and-drop blocks target: ".c-pb-toolbox-blocks-group__blocks > * .c-pb-toolbox-block__content:first-of-type" interaction_mode: draggable blocks: - type: text params: text_translation_key: Drag-and-drop blocks from the sidebar to the dashboard to customize it ``` To learn how to customize your scenarios even further with PHP code, see [Customize product tour](https://doc.ibexa.co/en/latest/administration/back_office/customize_product_tour/index.md). # Customize scenarios with PHP code Editions: LTS Update You can customize the product tour scenarios with the [`RenderProductTourScenarioEvent`](https://doc.ibexa.co/en/latest/api/event_reference/integrated_help_events/index.md) event. This event is dispatched before a product tour scenario is rendered. You can use it to: - modify tour steps based on user permissions or roles - add or remove steps dynamically - change block content based on runtime conditions - integrate custom data into tour scenarios With the following example, a custom onboarding scenario is built. It starts only when the current user has a pending [notification](https://doc.ibexa.co/projects/userguide/en/5.0/getting_started/notifications/). First, define a custom product tour scenario. It contains a placeholder step with a single block. ``` ibexa: system: admin_group: product_tour: notifications: type: 'targetable' steps: placeholder_step: step_title_translation_key: 'This is a placeholder step' target: '.ibexa-header-user-menu__notifications-toggler' blocks: - type: text params: text_translation_key: 'This is a placeholder block, modified during event subscriber execution' ``` Then, create a subscriber that modifies the scenario. ``` ['onRenderScenario'], ]; } public function onRenderScenario(RenderProductTourScenarioEvent $event): void { $scenario = $event->getScenario(); $steps = $scenario->getSteps(); if ($scenario->getIdentifier() !== 'notifications') { return; } foreach ($steps as $step) { $scenario->removeStep($step); } if (!$this->hasUnreadNotifications()) { return; } $customStep = new ProductTourStep(); $customStep->setIdentifier('custom_step_identifier'); $customStep->setInteractionMode('clickable'); $customStep->setTarget('.ibexa-header-user-menu__notifications-toggler'); $customStep->setTitle('You have unread notifications'); $customStep->addBlock(new TextBlock('Click here to preview your unread notifications.')); $customStep->addBlock(new LinkBlock( 'https://doc.ibexa.co/projects/userguide/en/latest/getting_started/notifications/', 'Learn more about notifications' )); $scenario->addStep($customStep); } private function hasUnreadNotifications(): bool { return $this->notificationService->getPendingNotificationCount() > 0; } } ``` The subscriber executes the following actions: - makes sure the correct scenario is being processed - removes all the existing scenario steps - verifies that the current user has a pending notification - adds a custom clickable step to highlight the unread notification *[Image: Scenario built with PHP triggered on unread notification]* # Customize search suggestion In the back office, when you start typing in the search field on the top bar, suggestions about what you could be looking for show up directly under the field. For more information about using this feature to search for content, see [User Documentation](https://doc.ibexa.co/projects/userguide/en/5.0/search/search_for_content/). ## Configuration By default, suggestions start showing up after the user types in at least 3 characters, and 5 suggestions are presented. This can be changed with the following [scoped](https://doc.ibexa.co/en/latest/multisite/multisite_configuration/#scope) configuration: ``` ibexa: system: : search: min_query_length: 3 result_limit: 5 ``` ## Add custom suggestion source You can add a suggestion source by listening or subscribing to `Ibexa\Contracts\Search\Event\BuildSuggestionCollectionEvent`. During this event, you can add, remove, or replace suggestions by updating its `SuggestionCollection`. After this event, the suggestion collection is sorted by score and truncated to a number of items set in [`result_limit`](#configuration). > **Tip: Tip** > > You can list listeners and subscribers with the following command: > > ``` > php bin/console debug:event BuildSuggestionCollectionEvent > ``` The following example is boosting product suggestions. It's a subscriber that passes after the default one (because priority is set to zero), adds matching products at a score above the earlier content suggestions, and avoids duplicates. - If the suggestion source finds a number of matching products that is equal or greater than the `result_limit`, only those products end up in the suggestion. - If it finds less than `result_limit` products, those products are on top of the suggestion, followed by items from another suggestion source until the limit is met. - If it doesn't find any matching products, only items from the default suggestion source are shown. This example event subscriber is implemented in the `src/EventSubscriber/MySuggestionEventSubscriber.php` file. It uses [`ProductService::findProducts`](https://doc.ibexa.co/en/latest/product_catalog/product_api/#products), and returns the received event after having manipulated the `SuggestionCollection`: ``` ['onBuildSuggestionCollectionEvent', -1], ]; } public function onBuildSuggestionCollectionEvent(BuildSuggestionCollectionEvent $event): BuildSuggestionCollectionEvent { $suggestionQuery = $event->getQuery(); $suggestionCollection = $event->getSuggestionCollection(); $text = $suggestionQuery->getQuery(); $words = explode(' ', (string) preg_replace('/\s+/', ' ', $text)); $limit = $suggestionQuery->getLimit(); try { $productQuery = new ProductQuery(null, new Criterion\LogicalOr([ new Criterion\ProductName(implode(' ', array_map(static fn (string $word): string => "$word*", $words))), new Criterion\ProductCode($words), new Criterion\ProductType($words), ]), [], 0, $limit); $searchResult = $this->productService->findProducts($productQuery); if ($searchResult->getTotalCount()) { $maxScore = 0.0; $suggestionsByContentIds = []; /** @var \Ibexa\Contracts\Search\Model\Suggestion\ContentSuggestion $suggestion */ foreach ($suggestionCollection as $suggestion) { $maxScore = max($suggestion->getScore(), $maxScore); $suggestionsByContentIds[$suggestion->getContent()->id] = $suggestion; } /** @var \Ibexa\ProductCatalog\Local\Repository\Values\Product $product */ foreach ($searchResult as $product) { $contentId = $product->getContent()->id; if (array_key_exists($contentId, $suggestionsByContentIds)) { $suggestionCollection->remove($suggestionsByContentIds[$contentId]); } $productSuggestion = new ProductSuggestion($maxScore + 1, $product); $suggestionCollection->append($productSuggestion); } } } catch (\Throwable $throwable) { $this->logger->error($throwable); } return $event; } } ``` To have the logger injected thanks to the `LoggerAwareTrait`, this subscriber must be registered as a service: ``` services: #… App\EventSubscriber\MySuggestionEventSubscriber: ~ ``` To represent the product suggestion data, a `ProductSuggestion` class is created in `src/Search/Model/Suggestion/ProductSuggestion.php`: ``` getName()); $this->product = $product; } public function getProduct(): Product { return $this->product; } } ``` This representation needs a normalizer to be transformed into a JSON. `ProductSuggestionNormalizer::supportsNormalization` returns that this normalizer supports `ProductSuggestion`. `ProductSuggestionNormalizer::normalize` returns an array of scalar values which can be transformed into a JSON object. Alongside data about the product, this array must have a `type` key, whose value is used later for rendering as an identifier. In `src/Search/Serializer/Normalizer/Suggestion/ProductSuggestionNormalizer.php`: ``` */ public function normalize($object, ?string $format = null, array $context = []): array { /** @var \App\Search\Model\Suggestion\ProductSuggestion $object */ return [ 'type' => 'product', 'name' => $object->getName(), 'productCode' => $object->getProduct()->getCode(), 'productTypeIdentifier' => $object->getProduct()->getProductType()->getIdentifier(), 'productTypeName' => $object->getProduct()->getProductType()->getName(), ]; } public function supportsNormalization($data, ?string $format = null, array $context = []): bool { return $data instanceof ProductSuggestion; } public function getSupportedTypes(?string $format): array { return [ ProductSuggestion::class => true, ]; } } ``` This normalizer is added to suggestion normalizers by decorating `ibexa.search.suggestion.serializer` and redefining its list of normalizers: ``` services: #… App\Search\Serializer\Normalizer\Suggestion\ProductSuggestionNormalizer: autoconfigure: false app.search.suggestion.serializer: decorates: ibexa.search.suggestion.serializer class: Symfony\Component\Serializer\Serializer autoconfigure: false arguments: $normalizers: - '@App\Search\Serializer\Normalizer\Suggestion\ProductSuggestionNormalizer' - '@Ibexa\Search\Serializer\Normalizer\Suggestion\ContentSuggestionNormalizer' - '@Ibexa\Search\Serializer\Normalizer\Suggestion\LocationNormalizer' - '@Ibexa\Search\Serializer\Normalizer\Suggestion\ParentLocationCollectionNormalizer' - '@Ibexa\Search\Serializer\Normalizer\Suggestion\SuggestionCollectionNormalizer' $encoders: - '@serializer.encoder.json' ``` > **Tip: Tip** > > At this point, it's possible to test the suggestion JSON. The route is `/suggestion` with a GET parameter `query` for the searched text. > > For example, log in to the back office to have a session cookie, then access the route through the back office SiteAccess, such as `/admin/suggestion?query=platform`. If you have a product with "platform" in its name, it is returned as the first suggestion. A JavaScript renderer displays the normalized product suggestion. This renderer is wrapped in an immediately executed function. This wrapping function must define a rendering function and register it as a renderer. It's registered as `autocomplete.renderers.` by using the type identifier defined in the normalizer. ``` (function (global, doc, ibexa, Routing) { const renderItem = (result, searchText) => { // Compute suggestion item's HTML return html; } ibexa.addConfig('autocomplete.renderers.', renderItem, true); })(window, document, window.ibexa, window.Routing); ``` To fit into the back office design, you can take HTML structure and CSS class names from an existing suggestion template `vendor/ibexa/admin-ui/src/bundle/Resources/views/themes/admin/ui/global_search_autocomplete_content_item.html.twig`. To allow template override and ease HTML writing, the example is also loading a template to render the HTML. Here is a complete `assets/js/admin.search.autocomplete.product.js` from the product suggestion example: ``` (function (global, doc, ibexa, Routing) { const renderItem = (result, searchText) => { const globalSearch = doc.querySelector('.ibexa-global-search'); const { highlightText } = ibexa.helpers.highlight; const autocompleteHighlightTemplate = globalSearch.querySelector('.ibexa-global-search__autocomplete-list').dataset .templateHighlight; const { getContentTypeIconUrl, getContentTypeName } = ibexa.helpers.contentType; const autocompleteItemTemplate = globalSearch.querySelector('.ibexa-global-search__autocomplete-product-template').dataset .templateItem; return autocompleteItemTemplate .replace('{{ productHref }}', Routing.generate('ibexa.product_catalog.product.view', { productCode: result.productCode })) .replace('{{ productName }}', highlightText(searchText, result.name, autocompleteHighlightTemplate)) .replace('{{ productCode }}', result.productCode) .replace('{{ productTypeIconHref }}', getContentTypeIconUrl(result.productTypeIdentifier)) .replace('{{ productTypeName }}', result.productTypeName); }; ibexa.addConfig('autocomplete.renderers.product', renderItem, true); })(window, document, window.ibexa, window.Routing); ``` To be loaded in the back office layout, this file must be added to Webpack entry `ibexa-admin-ui-layout-js`. At the end of `webpack.config.js`, add it by using `ibexaConfigManager`: ``` //… const ibexaConfigManager = require('./ibexa.webpack.config.manager.js'); ibexaConfigManager.add({ ibexaConfig, entryName: 'ibexa-admin-ui-layout-js', newItems: [path.resolve(__dirname, './assets/js/admin.search.autocomplete.product.js')], }); ``` The renderer, `renderItem` function from `admin.search.autocomplete.product.js`, loads an HTML template from a wrapping DOM node [dataset](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset). This wrapping node exists only once and the renderer loads the template several times. The example template for this wrapping node is stored in `templates/themes/admin/ui/global_search_autocomplete_product_template.html.twig` (notice the CSS class name used by the renderer to reach it): ```
    ``` - At HTML level, it wraps the product item template in its dataset attribute `data-template-item`. - At Twig level, it includes the item template, replaces Twig variables with the strings used by the JS renderer, and passes it to the [`escape` filter](https://twig.symfony.com/doc/3.x/filters/escape.html) with the HTML attribute strategy. To be present, this wrapping node template must be added to the `admin-ui-global-search-autocomplete-templates` group of tabs components: ``` services: #… ibexa.search.autocomplete.product_template: parent: Ibexa\AdminUi\Component\TabsComponent arguments: $template: '@@ibexadesign/ui/global_search_autocomplete_product_template.html.twig' $groupIdentifier: 'global-search-autocomplete-product' tags: - { name: ibexa.twig.component, group: global-search-autocomplete-templates } ``` The template for the product suggestion item follows, named `templates/themes/admin/ui/global_search_autocomplete_product_item.html.twig`: ```
  • {{ product_name }}
    {{ product_code }}
    {{ product_type_name }}
  • ``` ## Replace default suggestion source To replace the default suggestion source, [decorate](https://symfony.com/doc/7.4/service_container/service_decoration.html) the built-in `BuildSuggestionCollectionEvent` subscriber with your own: ``` services: #… App\EventSubscriber\MySuggestionEventSubscriber: decorates: Ibexa\Search\EventDispatcher\EventListener\ContentSuggestionSubscriber ``` # Customize search sorting You can customize the **Sort by** menu in the back office search result page, for example, by adding new sort criteria. To do it, you must create a service that implements the `Ibexa\Contracts\Search\SortingDefinition\SortingDefinitionProviderInterface` and tag it with `ibexa.search.sorting_definition.provider`. The following example class implements `SortingDefinitionProviderInterface::getSortingDefinitions`, and adds two definitions to sort by section name. A sorting definition contains an identifier, a menu label, a list of content search Sort Clauses, which could be either [default](https://doc.ibexa.co/en/latest/search/sort_clause_reference/sort_clause_reference/#sort-clauses) or [custom](https://doc.ibexa.co/en/latest/search/extensibility/create_custom_sort_clause/index.md), and a priority value to position them in the menu. It also implements `TranslationContainerInterface::getTranslationMessages` to provide two default English translations in the `ibexa_search` namespace. Create the `src/Search/SortingDefinition/Provider/SectionNameSortingDefinitionProvider.php` file: ``` translator->trans('sort_definition.section_name_asc.label'), [ new SortClause\SectionName(Query::SORT_ASC), ], 333 ), new SortingDefinition( 'section_desc', $this->translator->trans('sort_definition.section_name_desc.label'), [ new SortClause\SectionName(Query::SORT_DESC), ], 369 ), ]; } public static function getTranslationMessages(): array { return [ (new Message('sort_definition.section_name_asc.label'))->setDesc('Sort by section A-Z'), (new Message('sort_definition.section_name_desc.label'))->setDesc('Sort by section Z-A'), ]; } } ``` Then add a service definition to `config/services.yaml`: ``` services: #… App\Search\SortingDefinition\Provider\SectionNameSortingDefinitionProvider: tags: - name: ibexa.search.sorting_definition.provider ``` You can extract a translation file with the `jms:translation:extract` command, for example, `php bin/console jms:translation:extract en --dir=src --output-dir=translations` to obtain the `translations/ibexa_search.en.xlf` file. You could also create it manually, as `translations/messages.en.yaml` file with the following contents: ``` sort_definition.section_name_asc.label: 'Sort by section A-Z' sort_definition.section_name_desc.label: 'Sort by section Z-A' ``` # Recent activity (Experience) (Commerce) Recent activity log displays last actions in the repository (whatever their origin is, for example, back office, REST, migration, CLI, or CRON). *[Image: Recent activity]* To learn more about its back office usage and the actions logged by default, see [Recent activity in User Documentation](https://doc.ibexa.co/projects/userguide/en/5.0/recent_activity/recent_activity/). ## Configuration and cronjob With some configuration, you can customize the log length in the database or on screen. A command maintains the log size in database, it should be scheduled through CRON. - The configuration `ibexa.system..activity_log.pagination.activity_logs_limit` sets the number of log items shown per page in the back office (default value: 25). A log item is a group of entries, or an entry without group. - The configuration `ibexa.repositories..activity_log.truncate_after_days` sets the number of days a log entry is kept before it's deleted by the `ibexa:activity-log:truncate` command (default value: 30 days). For example, the following configuration sets 15 days of life to the log entries on the `default` repository, and 20 context groups per page for the `admin_group` SiteAccess group: ``` ibexa: repositories: default: activity_log: truncate_after_days: 15 system: admin_group: activity_log: pagination: activity_logs_limit: 20 ``` To automate a regular truncation, the command `ibexa:activity-log:truncate` must be added to a crontab. To minimize the number of entries to delete, it's recommended to execute the command more than one time a day. For every exact hour, the cronjob line is: `0 * * * * cd [path-to-ibexa]; php bin/console ibexa:activity-log:truncate --quiet --env=prod` ## Permission and security The [`activity_log/read`](https://doc.ibexa.co/en/latest/permissions/policies/#activity-log) policy gives a role the access to the **Admin** -> **Activity list**, the dashboard's **Recent activity** block, and the user profile's **Recent activity**. It can be limited to "Only own logs" ([`ActivityLogOwner`](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#activity-log-owner-limitation)). The policy should be given to every roles having access to the back office, at least with the `ActivityLogOwner` owner limitation, to allow them to use the "Recent activity" block in the [default dashboard](https://doc.ibexa.co/en/latest/administration/dashboard/configure_default_dashboard/index.md) or their [custom dashboard](https://doc.ibexa.co/en/latest/administration/dashboard/customize_dashboard/index.md). This policy is required to view [activity log in user profile](https://doc.ibexa.co/projects/userguide/en/5.0/getting_started/get_started/#view-and-edit-user-profile), if [profile is enabled](https://doc.ibexa.co/en/latest/update_and_migration/from_4.5/update_from_4.5/#user-profile). > **Caution: Caution** > > Don't assign `activity_log/read` permission to the Anonymous role, even with the owner limitation, because this role is shared among all unauthenticated users. ## User privacy > **Caution: Caution** > > A username of the User who performs the action is logged. When acting through the web server, the User's IP address is also logged. Other access, such as console commands, doesn't log an IP. Your Data Protection Officer or GDPR representative should be aware of this, so they can ensure users are informed if needed, depending on your use case, jurisdiction, and company policy. > > For example, if a content edition feature, such as reader's comments, is available in the front office, the recent activity log records the front users' IPs. ## PHP API The `ActivityLogService` PHP API can be used to browse activity logs and write new entries. ### Searching in the Activity Log groups You can search among the activity log entry groups with the `ActivityLogService::findGroups` method, by passing an `Ibexa\Contracts\ActivityLog\Values\ActivityLog\Query` object. This `Query`'s constructor has four arguments: - `$criteria` - an array of criteria from `Ibexa\Contracts\ActivityLog\Values\ActivityLog\Criterion` combined as a logical AND. - `$sortClauses` - an array of `Ibexa\Contracts\ActivityLog\Values\ActivityLog\SortClause`. - `$offset` - a zero-based index integer indicating at which group to start, its default value is `0` (zero, nothing skipped). - `$limit` - an integer as the maximum returned group count, default is 25. See [Activity Log Search Criteria reference](https://doc.ibexa.co/en/latest/search/activity_log_search_reference/activity_log_criteria/index.md) and [Activity Log Search Sort Clauses reference](https://doc.ibexa.co/en/latest/search/activity_log_search_reference/activity_log_sort_clauses/index.md) to discover query possibilities. In the following example, log groups that contain at least one creation of a Content item are displayed in terminal, with a maximum of 10 groups within the last hour. It uses the default `admin` user that has a [permission](#permission-and-security) to list everyone's entries. ``` permissionResolver->setCurrentUserReference($this->userService->loadUserByLogin('admin')); foreach ($this->activityLogService->findGroups($query) as $activityLogGroup) { if ($activityLogGroup->getSource()) { $io->section($activityLogGroup->getSource()->getName()); } if ($activityLogGroup->getDescription()) { $io->text($activityLogGroup->getDescription()); } $table = []; foreach ($activityLogGroup->getActivityLogs() as $activityLog) { $name = "“{$activityLog->getObjectName()}”"; $content = $activityLog->getRelatedObject(); if ($content && method_exists($content, 'getName') && $content->getName() !== $activityLog->getObjectName()) { $name = "“{$content->getName()}” (formerly “{$activityLog->getObjectName()}”)"; } $table[] = [ $activityLogGroup->getLoggedAt()->format(\DateTime::ATOM), $activityLog->getObjectId(), $name, $activityLog->getAction(), $activityLogGroup->getUser() ? $activityLogGroup->getUser()->login : '', $activityLogGroup->getIp() ? $activityLogGroup->getIp()->getIp() : '', ]; } $io->table([ 'Logged at', 'Obj. ID', 'Object Name', 'Action', 'User', 'IP', ], $table); } return Command::SUCCESS; } } ``` ``` % php bin/console app:monitor-content-creation web --- --------------------------- --------- --------------------------- -------- ---------- ------------ Logged at Obj. ID Object Name Action User IP --------------------------- --------- --------------------------- -------- ---------- ------------ 2024-01-29T15:01:57+00:00 323 “Bar” (formerly “Folder”) create jane_doe 172.20.0.5 --------------------------- --------- --------------------------- -------- ---------- ------------ migration --------- Migrating file: create_foo_company --------------------------- --------- -------------------- -------------- ------- ---- Logged at Obj. ID Object Name Action User IP --------------------------- --------- -------------------- -------------- ------- ---- 2024-01-29T14:58:53+00:00 317 “Foo Company Ltd.“ create admin 2024-01-29T14:58:53+00:00 317 “Foo Company Ltd.“ publish admin 2024-01-29T14:58:53+00:00 318 “Members“ create admin 2024-01-29T14:58:53+00:00 318 “Members“ publish admin 2024-01-29T14:58:53+00:00 317 “Foo Company Ltd.“ create_draft admin 2024-01-29T14:58:53+00:00 317 “Foo Company Ltd.“ update admin 2024-01-29T14:58:53+00:00 317 “Foo Company Ltd.“ publish admin 2024-01-29T14:58:53+00:00 319 “Address Book“ create admin 2024-01-29T14:58:53+00:00 319 “Address Book“ publish admin 2024-01-29T14:58:53+00:00 317 “Foo Company Ltd.“ create_draft admin 2024-01-29T14:58:53+00:00 317 “Foo Company Ltd.“ update admin 2024-01-29T14:58:53+00:00 317 “Foo Company Ltd.“ publish admin 2024-01-29T14:58:53+00:00 320 “HQ“ create admin 2024-01-29T14:58:53+00:00 320 “HQ“ publish admin 2024-01-29T14:58:53+00:00 317 “Foo Company Ltd.“ create_draft admin 2024-01-29T14:58:53+00:00 317 “Foo Company Ltd.“ update admin 2024-01-29T14:58:53+00:00 317 “Foo Company Ltd.“ publish admin --------------------------- --------- -------------------- -------------- ------- ---- ``` ### Add custom Activity Log entries > **Caution: Caution** > > Keep activity logging as light as possible. Don't make database requests or heavy computation at logging time. Keep them for activity log list display time. #### Create an entry Your custom features can write into the activity log. First, inject `Ibexa\Contracts\ActivityLog\ActivityLogServiceInterface` into your PHP class from where you want to log an activity (such as a custom event subscriber, event listener, service, or controller). In the following example, an event subscriber is subscribing to an event dispatched by a custom feature. This event has the information needed by a log entry (see details after the example). ``` 'onMyFeatureEvent', ]; } public function onMyFeatureEvent(MyFeatureEvent $event): void { /** @var \App\MyFeature\MyFeature $object */ $object = $event->getObject(); $className = $object::class; $id = (string)$object->id; $action = $event->getAction(); $activityLog = $this->activityLogService->build($className, $id, $action); $activityLog->setObjectName($object->name); $this->activityLogService->save($activityLog); } } ``` `ActivityLogService::build()` function returns an `Ibexa\Contracts\ActivityLog\Values\CreateActivityLogStruct` which can then be passed to `ActivityLogService::save`. `ActivityLogService::build` has three arguments: - `$className` is a FQCN of the object actually manipulated by the feature, for example `Ibexa\Contracts\Core\Repository\Values\Content\Content::class` - `$id` is an ID or identifier of the manipulated object, for example, the Content ID cast to string - `$action` is an identifier of the performed object manipulation, or example, `create`, `update` or `delete` The returned `CreateActivityLogStruct` is always related to the currently logged-in user. You can still display activity log of an object which was deleted or renamed. To store the name of the log, you need to use `CreateActivityLogStruct::setName` before saving the log entry. This stored name can be used at the time of displaying information whether the associated object isn't available anymore, or to check if it has been renamed. #### Context group If you log several related entries at once, you can group them into a context. Context is a set of actions done for the same purpose, for example, it could group the actions of a CRON that fetches third party data and updates content items. The built-in contexts include: - `web` - groups actions made in the back office, like the update and the publishing of a new content item's version - `migration` - groups every action from a migration file execution A context group counts as one item in regard to `activity_logs_limit` configuration and `ActivityLogService::findGroups`'s `$limit` argument. To open a context group, use `ActivityLogService::prepareContext` which has two arguments: - `$source` - describes, usually through a short identifier, what is triggering the set of actions. For example, some already existing sources are `web` (incl. actions from the back office), `graphql`, `rest` and `migration` - `$description` - an optional, more specific contextualisation. For example, `migration` context source is associated with the migration file name in its context description. To close a context group, use `ActivityLogService::dismissContext`. In the following example, several actions are logged into one context group, even those triggered by a cascade outside the piece of code: - `my_feature` - `init` - `create` - `publish` - `simulate` - `complete` ``` $this->activityLogService->prepareContext('my_feature', 'Operation description'); $activityLogStruct = $this->activityLogService->build(MyFeature::class, $id, 'init'); $activityLogStruct->setObjectName("My Feature #$id"); $this->activityLogService->save($activityLogStruct); $contentCreateStruct = $this->contentService->newContentCreateStruct($this->contentTypeService->loadContentTypeByIdentifier('folder'), 'eng-GB'); $contentCreateStruct->setField('name', "My Feature Folder #$id", 'eng-GB'); $locationCreateStruct = new LocationCreateStruct(['parentLocationId' => 2]); $draft = $this->contentService->createContent($contentCreateStruct, [$locationCreateStruct]); $this->contentService->publishVersion($draft->versionInfo); $event = new MyFeatureEvent(new MyFeature(['id' => $id, 'name' => "My Feature #$id"]), 'simulate'); $this->eventDispatcher->dispatch($event); $activityLogStruct = $this->activityLogService->build(MyFeature::class, $id, 'complete'); $activityLogStruct->setObjectName("My Feature #$id"); $this->activityLogService->save($activityLogStruct); $this->activityLogService->dismissContext(); ``` Context groups can't be nested. If a new context is prepared when a context is already grouping log entries, this new context is ignored. To start a new context, make sure to previously dismiss the existing one. When displayed in the back office, a context group is folded below its first entry. The `my_feature` context from the example is folded below its first action, the `init` action. Other actions are displayed after you click the **Show more** button. *[Image: The example context group displayed on the Recent Activity page]* #### Display log entries To display your log entry, if your object's PHP class isn't already covered, you have to: - implement `ClassNameMapperInterface` to associate the class name with an identifier, - eventually create a `PostActivityListLoadEvent` subscriber if you need to load the object for the template, - create a template to display this class log entries. You can have a template that is: - specific to a class identifier and placed in `templates/themes//activity_log/ui/.html.twig` - specific to an action on an identifier and placed in `templates/themes//activity_log/ui//.html.twig` Template existence is tested in reverse order: if there is no action that specifies the template, the identifier's default is used. For the same identifier, you could have specific templates for few actions, and a default one for the remaining actions. A default template is used if no template is found for the identifier. The built-in default template `@ibexadesign/activity_log/ui/default.html.twig` has an empty `activity_log_description_widget` block and doesn't display anything for unknown objects. Your template can extend `@ibexadesign/activity_log/ui/default.html.twig`, and only redefine the `activity_log_description_widget` block for your objects. First, follow an example of a default template overriding the one from the bundle. It can be used during development as a fallback for classes that aren't mapped yet. ``` {% extends '@IbexaActivityLog/themes/admin/activity_log/ui/default.html.twig' %} {%- block activity_log_description_widget -%} {{ dump(log) }} {%- endblock activity_log_description_widget -%} ``` Here is an example of a `ClassNameMapperInterface` associating the class `App\MyFeature\MyFeature` with the identifier `my_feature`: ``` 'my_feature'; } public static function getTranslationMessages(): array { return [ (new Message('ibexa.activity_log.search_form.object_class.my_feature', 'ibexa_activity_log')) ->setDesc('My Feature'), ]; } } ``` This mapper also provides a translation for the class name in the **Filters** menu. This translation can be extracted with `php bin/console jms:translation:extract en --domain=ibexa_activity_log --dir=src --output-dir=translations`. To be taken into account, this mapper must be registered as a service: ``` services: App\ActivityLog\ClassNameMapper\MyFeatureNameMapper: ~ ``` Here is an example of a `PostActivityListLoadEvent` subscriber which loads the related object when it's an `App\MyFeature\MyFeature`, and attaches it to the log entry: ``` ['loadMyFeature'], ]; } public function loadMyFeature(PostActivityGroupListLoadEvent $event): void { $visitedIds = []; $list = $event->getList(); foreach ($list as $logGroup) { foreach ($logGroup->getActivityLogs() as $log) { if ($log->getObjectClass() !== MyFeature::class) { continue; } $id = (int)$log->getObjectId(); try { if (!array_key_exists($id, $visitedIds)) { $visitedIds[$id] = $this->myFeatureService->load($id); } if ($visitedIds[$id] === null) { continue; } $log->setRelatedObject($visitedIds[$id]); } catch (NotFoundException|UnauthorizedException) { $visitedIds[$id] = null; } } } } } ``` The following template is made to display the object of `App\MyFeature\MyFeature` (now identified as `my_feature`) when the action is `simulate`, so, it's named in `templates/themes/admin/activity_log/ui/my_feature/simulate.html.twig`. Thanks to the previous subscriber, the related object is available at display time: ``` {% extends '@ibexadesign/activity_log/ui/default.html.twig' %} {%- block activity_log_description_widget -%} {% if log.getRelatedObject() is not null %} {{- log.getRelatedObject().name -}} {% if log.getRelatedObject().name != log.getObjectName() %} (was named “{{ log.getObjectName() }}”) {% endif %} {% else %} {{ log.getObjectName() }} (which doesn't exist anymore) {% endif %} {%- endblock activity_log_description_widget -%} ``` ## REST API You can browse activity logs with REST API. For more information, see the [REST API reference](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Activity-Log). # Content management # Content management - [Content management product guide](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/content_management/content_management_guide/): Read the content management product guide and learn how to create, modify, and display information to the target audience. - [Content model](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/content_management/content_model/): Ibexa DXP's content model relies on content items that are instances of content types and contain content fields. - [Locations](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/content_management/locations/): Locations hold published content items and can be used to control visibility. - [Field type reference](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/content_management/field_types/field_type_reference/field_type_reference/): Ibexa DXP offers a range of built-in field types that cover most common needs when creating content. - [Pages](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/content_management/pages/pages/): Pages are block-based special types of content that editors can create and modify by using a visual drag-and-drop editor. - [Forms](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/content_management/forms/forms/): Forms are a type of content item that you can use to improve the functionality of your website. - [Taxonomy](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/content_management/taxonomy/taxonomy/): A taxonomy uses tags to categorize and organize content - [Workflow](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/content_management/workflow/workflow/): Workflow controls how content items pass between stages and allows setting up editorial flows, for example for reviews and proofreading. - [Data migration](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/content_management/data_migration/data_migration/): Data migration enables you to import and export repository data by using YAML files. # Content management product guide ## What is content management The term “content management” covers all the tasks that you need to perform to create, edit and present content to its intended audience. The content management model applied in Ibexa DXP lies at the foundation of the entire system. A system that relies on roles and permissions controls access to content items and is granular and powerful enough to be used in managing user accounts, corporate accounts, products, or process definitions. ## Availability Content management capabilities are available in all Ibexa DXP editions. ## How does it work Ibexa DXP revolves around content management. Many things here are content items, including: - sites - folders - pages - articles or posts - products - forms - media (for example, images or videos) - user accounts You can set up content structure, define the templates to be filled with content, and assign different areas of the structure to your editors. Next steps would be to create the actual content, and then classify content items, and organize them as necessary. You can then publish the content directly, by building a website or a web store, or by using external systems together with a [headless CMS](https://developers.ibexa.co/headless-cms) that relies on the Ibexa DXP technology. ## Content structure All content in Ibexa DXP is organized hierarchically, into what is called a [**content tree**](https://doc.ibexa.co/en/latest/administration/back_office/content_tree/index.md). This tree-like structure repeats throughout the system, and applies to content, taxonomies, categories, and the like. Traditional as the structure may look, with relations and multiple location support, a single content item can be referenced by another content item and accessed from different places of the tree, which allows you to build complex architectures with multiple locales and output channels. *[Image: Content structure in a Content Browser]* ## Content model A structure of elements that *store* content information is referred to as the **content model**. Ibexa DXP comes with a predefined content model that includes a broad set of various field types and several content types. You can customize and adapt the content model to your organization's needs and the type of output channel that you use. If need be, development teams can [create new field types](https://doc.ibexa.co/en/latest/tutorials/generic_field_type/creating_a_point2d_field_type/index.md), to enhance editor and visitor experiences. Content managers or even editors can then apply such field types when they modify existing or create new content types. The editing interface lets all users, including those with no coding experience, create or modify certain areas of the content model. For technical details, see [a Content model](https://doc.ibexa.co/en/latest/content_management/content_model/#content-model). ### Field types [Field types](https://doc.ibexa.co/en/latest/content_management/field_types/field_types/index.md) are the smallest elements of the content model’s structure. Ibexa DXP comes with many built-in field types that cover most common needs, for example, Text line, RichText, Integer, Measurement, or Map location. Their role is to: - store data - validate input data - make the data searchable - display fields of a given field type For a complete list of available field types, see [field type reference](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/field_type_reference/index.md). *[Image: Field types and fields]* ### Fields Once you use a field type to design and build a content type definition, and define its settings, it becomes a field. Fields can be as simple as Name, based on a Text line field type, or as complex as page, based on a landing page field type, with multiple options to set and choose from: *[Image: Landing page field settings]* ### Content types Life gets easier when you have templates to fill in with content. Content types are such templates, which editors use to create content items. Content types define what fields are available in the content item. Ibexa DXP comes with several basic content types, and creating new ones, editing, and deleting them is done by using a visual interface, with no coding skills needed. *[Image: Content types vs. content items]* ### Content items Content items are pieces of content, such as, for example, products, articles, blog posts, or media. In Ibexa DXP, everything is a content item — not only pages, articles or products, but also all media (for example, images or videos) or even user accounts. Each content item, apart from its name and identifier, contains a composition of fields, which differs depending on the type of content. For example, articles might have for example, a title, an author, a body, and an image, while products may have, for example, a name, category, price, size, or color. ### Forms Forms could be seen as a special kind of content items, because their role is to gather information from website users and not present it. You create them from basic form fields available in Ibexa DXP. By adding forms to the website, you can increase the website’s functionality and improve user experience. Certain editions of Ibexa DXP come with a visual [Form Builder](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/work_with_forms/). ## Content management capabilities Each content item has at least one location within the content tree, and can have several versions and multiple translations. It can also have related assets, such as images or other media, and assigned keywords, or tags. You can use these characteristics in combination with system features to create the most comprehensive and functional digital presence for your organization. ### Content characteristics #### Locations When a content item is created and published, it's assigned a place in the content tree, designated by a location ID. A single content item can have more than one location ID, which means that the same content can be found on different branches of the tree. However, a single location can have only one content item assigned to it. *[Image: Locations]* Locations can be used to control the availability of content items to end users: you can [hide specific locations](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/content_organization/manage_locations_urls/#hide-locations) of a content item, while others remain available. By [swapping locations](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/content_organization/manage_locations_urls/#swap-locations), you can immediately replace an obsolete version of a content item with an updated one. #### Versions Content items can have several [versions](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/content_versions/). By default, there are three version statuses available: draft, published, and archived. Before they're published, drafts can be routed between different user roles for review and approval. *[Image: Versions]* Editors can [compare different content item versions](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/workflow_management/work_with_versions/#compare-versions) by using the Compare versions feature. #### Translations Content items can have more than one [translation](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/translate_content/). If a website has different fronts, for different locales, and different language versions of content exist, Ibexa DXP serves the one that matches the locale. *[Image: Translations]* Editors can compare different translations of the same content items with the Compare versions feature mentioned above. #### Relations A [relation](https://doc.ibexa.co/en/latest/content_management/content_relations/index.md) can exist between any two content items in the content tree. For example, blog posts featured in the website's main page are in a relation with the page that they're embedded in. Or, instead of direct attachments, an article can use images that are separate content items outside the article, and are referenced through a relation. ## Content arrangement In Ibexa DXP, content items can be moved and copied between branches of the content tree. These operations, like in your computer’s file system, can apply both to individual content items and folders or groups. *[Image: Content organization operations]* Content items can be hidden when necessary, for example, until a certain event, like a Holiday Sale, or Board announcement comes. Hidden content items aren't visible to website visitors and are greyed out in the content tree. *[Image: Hidden content item]* Editors can also move obsolete content items to Trash, and ultimately delete them. *[Image: Delete confirmation dialog box]* ## Content classification There are multiple tools within Ibexa DXP that help content managers classify content or restrict access to content to certain recipients. ### Taxonomy With taxonomy you can create tags or keywords within a tree structure and assign them to content items. This way you can classify content and make it easier for end users to find the content they need, or browse and view content from a category that suits them best. *[Image: Taxonomy principles]* ### Access control When your Ibexa DXP instance has multiple contributors and visitors, administrators can give them access to different areas of the website and different capabilities. It's done by creating roles, with each role having a different set of [permissions](https://doc.ibexa.co/en/latest/permissions/permission_overview/index.md), the most fitting example being the `content/edit` permission limited to an `Articles/BookReviews/Historical` subtree of the content tree. In the next steps, after you create user groups, you’d assign roles to these groups, and add individual users to each of such groups. For more technical information about permissions and limitations, see [Permission use cases](https://doc.ibexa.co/en/latest/permissions/permission_use_cases/index.md). There are, however, mechanisms to control access to content with even more convenience. ### Sections You can divide your content tree into nominal parts to better organize it. Once you have defined sections, for example, Media or Forms, and assigned them to content items, you can decide which roles have access to which section of the tree. The setting is inherited, which means that a child content item inherits a value of this setting from its parent. Changing a section setting doesn't result in moving a content item to a different location within a content tree. *[Image: Members of the Media Section]* ### Object states While reviewing the details of each individual content item in your content tree, you can assign a state to it, for example, “Locked” or “Not locked”. Then you can set a permission that allows or denies users access to content items in a specific state. This setting isn't inherited. *[Image: Object states in content item’s Details]* ### User segments Although segments aren't meant to classify content, they could fall into this category, because their role is about targeting users, and not controlling their access to content. With segments, you can reach specific groups, or categories, of visitors with specific information about content or products that could be of their interest. For example, you can build Pages that contain different recommendations, depending on who is visiting them. *[Image: A segment group with two user segments]* ## How to get started Once you have integrated the headless implementation, installed a local instance of Ibexa DXP or set up an instance on Ibexa Cloud, you're ready to employ the content management features to good use. Since content management is an ongoing process, and, in your implementation, you might prefer focusing on other areas of configuration, the order of operations below is by all means conventional. **1. Create a content model** Any content that you might want to deliver to a viewer can be structured and split into smaller elements. Reverse-engineer the intended concepts into individual fields, which can be categorized, and then picked from categories and combined into content items. Reuse existing fields types or [customize them to fit your needs](https://doc.ibexa.co/en/latest/content_management/field_types/create_custom_generic_field_type/index.md), then [create content types](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/create_edit_content_items/). **2. Define permissions** Although this step isn't directly related to content management, it's a good time to [set up user roles and permissions](https://doc.ibexa.co/projects/userguide/en/5.0/permission_management/work_with_permissions/), which users would need to work with content. **3. Author content** [Create various content items](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/create_edit_content_items/), such as pages, articles, forms, or media. While you fill fields with content, several actions are there to help you with your task. You can pause and resume the work, preview the results, or send content for review. *[Image: Send to review]* **4. Publish** Again, this isn't part of content management, but at this point you can [publish](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/publish_instantly/) it right away or [schedule content for publication](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/schedule_publishing/). **5. Organize content** Organize the content of your website by copying or moving content items, [controlling Locations and URL addresses](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/content_organization/manage_locations_urls/). Then work with Tags, sections and object states to [classify](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/content_organization/classify_content/#sections) it. ## Benefits The most important benefits of using Content management capabilities of Ibexa DXP can be gathered into the following groups: 1. Content management capabilities help reduce the effort required to maintain, administer, and distribute digital content, so that you can focus on business operations. 2. Segmentation, translations, and taxonomy make it possible to assist and target visitors from different backgrounds and markets. 3. Granular access control ensures that no content in your control lands before the unauthorized eyes. ## Use cases Ibexa DXP’s capabilities prove indispensable in many applications. ### Corporate website The most common use case for a comprehensive content management system like Ibexa DXP would be creating and maintaining a multinational company’s digital presence, with both public and intranet channels, multiple websites with overlapping content structures, and business partners and end-customers alike wanting to connect through different channels to access public and classified content. ### B2C web store Content management could lie at a foundation of a successful global web store, where customers connect through localized websites and branded mobile apps: individual products can have multiple variants with differing related assets, product descriptions must be available in multiple language versions, and access to certain areas of the store depends on both a country and a segment that the customer comes from. ### B2B store Extensive content management capabilities would prove themselves in a setting, where multiple buyers from different partner companies connect to an industry leader’s trading website, and they expect to find well organized product code (SKU) catalogs that contain basic product information. From there they would like to access detailed specifications, white papers and application notes. The same products could come with different brands and at different price points, depending on the customer segment or origin. # Content model ## Content model overview The content structure in Ibexa DXP is based on content items. A content item represents a single piece of content, for example, an article, a blog post, an image, or a product. Each content item is an instance of a content type. > **Tip: Tip** > > An introduction to the content model for non-developer users is available in [User Documentation](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/content_model/). ## Content items A content item consists of: - [Content information](#content-information) - [Fields](#fields), defined by the [content type](https://doc.ibexa.co/en/latest/administration/content_organization/content_types/index.md). The fields can cover data ranging from single variables and text lines to media files or blocks of formatted text. #### Content information General information about a content item is stored in a [`ContentInfo`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-ContentInfo.html) object. `ContentInfo` doesn't include fields. It contains following information: **`id`** - the unique ID of the Content object. These numbers aren't recycled, so if an item is deleted, its ID isn't reused when a new one is created. **`contentTypeId`** - the unique numerical ID of the content type, on which the content item is based. **`name`** - the name is generated automatically based on a [pattern specified in the content type definition](https://doc.ibexa.co/en/latest/administration/content_organization/content_types/#content-name-pattern). The name is in the main language of the content item. > **Note: Note** > > `name` is always searchable, even if the field(s) used to generate it aren't. **`sectionId`** - the unique number of the section to which the content item belongs. New content items are placed in the Standard section by default. This behavior can be changed, but content must always belong to some section. For more information, see [Sections](https://doc.ibexa.co/en/latest/administration/content_organization/sections/index.md). **`currentVersionNo`** - current version number is the number of the published version or of a newly created draft (which is 1). **`published`** - true if a published version exists, otherwise false. **`ownerId`** - ID of the user who initially created the content item. It's set by the system the first time the content item is published. The ownership of an item cannot be modified and doesn't change even if the owner is removed from the system. **`modificationDate`** - date and time when the content item was last modified. It's set by the system and cannot be modified manually, but changes every time the item is published again. **`publishedDate`** - date and time when the content item was published for the first time. It's set by the system and cannot be modified. **`alwaysAvailable`** - indicates if the content item is shown in the main language when it's not present in another requested language. It's [set per content type](https://doc.ibexa.co/en/latest/content_management/content_availability/index.md). **`remoteId`** - a global unique ID of the content item. Accepts up to 100 characters. Cannot contain non-printable characters and control sequences (anything in ASCII range `\x00` - `\x1F`). It's recommended to either let this value be generated by the Public PHP API as an MD5 hash, or at least to generate it as a hash (for example, one from SHA family). **`mainLanguageCode`** - the main language code of the content item. If the `alwaysAvailable` flag is set to true, the content item is shown in this language when the requested language doesn't exist. **`mainLocationId`** - identifier of the content item's main [location](https://doc.ibexa.co/en/latest/content_management/locations/index.md). **`status`** - status of the content item. It can have three statuses: 0 – *draft*, 1 – *published* and 2 – *archived*. When an item is created, its status is set to *draft*. After publishing the status changes to *published*. When a published content item is moved to Trash, the item becomes *archived*. If a published item is removed from the Trash (or removed without being put in the Trash first), it's permanently deleted. *[Image: Diagram of an example content item]* The fields of a content item are defined by the content type to which the content item belongs. ## Fields A field is the smallest unit of storage in the content model and the building block of all content items. Every field belongs to a field type. Beyond the built-in set of field types, you can [create your own](https://doc.ibexa.co/en/latest/content_management/field_types/create_custom_generic_field_type/index.md). ### Field value validation The values entered in a field may undergo validation, which means the system makes sure that they're correct for the chosen field type and can be used without a problem. Validation depends on the settings of a particular field type. It cannot be turned off for a field if its field type supports it. ### Field details Aside from the field type, the field definition in a content type provides the following information: **Name** – a user-friendly name that describes the field. This name is used in the interface, but not internally by the system. It can consist of letters, digits, spaces, and special characters (the maximum length is 255 characters). If no name is provided, a unique one is automatically generated. **Identifier** – an identifier for internal use, for example, in configuration files, templates, or PHP code. It can only contain lowercase letters, digits and underscores (the maximum length is 50 characters). This identifier is also used in name patterns for the content type. **Description** – a detailed description of the field. **Required** – a flag which indicates if the field is required for the system to accept the content item. By default, if a field is flagged as Required, a user isn't able to publish a content item without filling in this field. > **Note: Note** > > You can use the `ContentService::validate()` method to decide whether the required fields or whole content items are checked for completeness at other stages of the editing process. > > The Required flag is in no way related to field validation. A field's value is validated whether the field is set as required or not. **[Searchable](https://doc.ibexa.co/en/latest/search/search/index.md)** – a flag which indicates if the value of the field is indexed for searching. The Searchable flag isn't available for some fields, because some field types don't allow searching through their values. **[Translatable](https://doc.ibexa.co/en/latest/multisite/languages/languages/index.md)** – a flag which indicates if the value of the field can be translated. It's independent of the field type, which means that even fields such as "Float" or "Image" can be set as translatable. Depending on the field type, there may also be other, specific information to fill in. For example, the "Country" field type allows you to select the default country, and to allow selecting multiple countries at the same time. *[Image: Diagram of content model]* > **Tip: Tip** > > You can disable the possibility to edit specific field details per field type by [adding custom service definition for `ModifyFieldDefinitionsCollectionTypeExtension`](https://doc.ibexa.co/en/latest/content_management/field_types/customize_field_type_metadata/index.md). ## Content versions Each content item can have multiple versions. Each version has one of the following statuses: *draft*, *archived* or *published*. A new version is created every time a content item is edited. The previous published version isn't modified. Only one version can be published at the same time. When you publish a new version, the previous published version changes its status to Archived. The number of preserved archived versions is set in `ibexa.repositories.default.options.default_version_archive_limit`. By default it's set to 5. A new version is also created when a new [language](https://doc.ibexa.co/en/latest/multisite/languages/languages/index.md) is added to the content item. ## Products Products are a special type of content that holds products you can manage with the product catalog capabilities. For more information, see [Product catalog](https://doc.ibexa.co/en/latest/product_catalog/product_catalog/index.md). # Locations When a new content item is published, it's automatically placed in a new location. All locations form a tree which is the basic way of organizing content in the system. Every published content item has a location and, as a consequence, also a place in this tree. *[Image: Content tree - locations]* A content item receives a location only once it has been published. This means that a new unpublished draft doesn't have a location yet. You can find drafts in the **Drafts** tab in the **Content** menu. *[Image: Drafts]* A content item can have more than one location. It's then present in two or more places in the tree. For example, an article can be at the same time under "Local news" and "Sports news". Even in such a case, one of these places is always the main location. You can change the main location in the back office in the **Locations** tab, or [through the API](https://doc.ibexa.co/en/latest/content_management/content_api/managing_content/#changing-the-main-location). *[Image: Locations]* ## Top level locations The content tree is hierarchical. It has an empty root location at the top and a structure of dependent locations below it. Every location (aside from the root) has one parent location and can have any number of children. Top level locations are direct children of the root of the tree. The root has location ID 1, isn't related to any content items and should not be used directly. Under this root there are preset top level locations in each installation which cannot be deleted. ### Content The top level location for the actual contents of a site can be viewed by selecting the **Content structure** tab in the Content mode interface. *[Image: Content structure]* This part of the tree is typically used, for example, for organizing folders, articles, or information pages. The default ID number of this location is 2, but it can be [modified via configuration](https://doc.ibexa.co/en/latest/administration/configuration/repository_configuration/#top-level-locations). It contains a Folder content item. ### Media **Media** is the top level location which stores and organizes information that is frequently used by content items located below the **Content** node. *[Image: Media]* It usually contains images, animations, documents and other files. The default ID number of the **Media** location is 43, but it can be [modified via configuration](https://doc.ibexa.co/en/latest/administration/configuration/repository_configuration/#top-level-locations). It contains a Folder content item. ### Users **Users** is the top level location that contains the built-in system for managing user accounts. *[Image: Users in Admin panel]* A user is simply a content item of the user account content type. The users are organized within user group content items below this location. In other words, the **Users** location contains the actual users and user groups, which can be viewed by selecting the **Users** tab in the **Admin** Panel. The default ID number of the **Users** location is 5. It contains user group content items. ### Forms (Experience) (Commerce) **Forms** is the top level location that is intended for Forms created using the [Form Builder](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/work_with_forms/#create-forms). *[Image: Forms]* ### Other top level locations You should not add any more content directly below location 1, but instead store any content under one of those top-level locations. ## Location visibility Location visibility allows you to control which parts of the content tree are available on the front page. *[Image: Location visibility]* Once a content item is published, it cannot be un-published. When the location of a content item is hidden, the system doesn't display it on the website. > **Caution: Visibility and permissions** > > The [visibility switcher](https://doc.ibexa.co/en/latest/content_management/locations/#location-visibility) is a convenient feature for withdrawing content from the frontend. It acts as a filter in the frontend by default. You can choose to respect it or ignore it in your code. It isn't permission-based, and **doesn't restrict access to content**. Hidden content can be read through other means, like the REST API. > > If you need to restrict access to a given content item, you could create a role that grants read access for a given [**Section**](https://doc.ibexa.co/en/latest/administration/content_organization/sections/index.md) or [**Object State**](https://doc.ibexa.co/en/latest/administration/content_organization/object_states/index.md), and set a different section or object state for the given content. Or use other permission-based [**Limitations**](https://doc.ibexa.co/en/latest/permissions/limitations/index.md). If a content item is hidden, it's invisible in all its locations. If a location is hidden, all of its descendants in the tree are hidden as well. This means that there are three different visibility statuses: - Visible - Hidden - Hidden by superior All locations and content items are visible by default. If a location is made invisible manually, its status is set to Hidden. All locations under it change status to Hidden by superior. A content item is Hidden by superior only in locations in which it has a parent location with the Hidden status. In the following example, the **Content item 1** is Hidden by superior in the **Location A** while still visible in the **Location B**. *[Image: Visibility in two locations]* From the visitor's perspective a location behaves the same whether its status is Hidden or Hidden by superior – it's unavailable on the front page. The difference is that a location Hidden by superior cannot be revealed separately from their parent(s). It only becomes visible once all of its parent locations are made visible again. A Hidden by superior status doesn't override a Hidden status. This means that if a location is Hidden manually and later one of its ancestors is hidden as well, the first location's status doesn't change – it remains Hidden (not Hidden by superior). If the ancestor location is made visible again, the first location still remains hidden. The way visibility works can be illustrated using the following scenarios: #### Hiding a visible location *[Image: Hiding a visible location]* When you hide a location that was visible before, it gets the status Hidden. Its child locations are Hidden by superior. The visibility status of child locations that were already Hidden or Hidden by superior doesn't change. #### Hiding a location which is Hidden by superior *[Image: Hiding a location which is Hidden by superior]* When you explicitly hide a location which was Hidden by superior, it gets the status Hidden. Since the underlying locations are already either Hidden or Hidden by superior, their visibility status doesn't changed. #### Revealing a location with a visible ancestor *[Image: Revealing a location with a visible ancestor]* When you reveal a location which has a visible ancestor, this location and its children become visible. However, child locations that were explicitly hidden by a user keep their Hidden status (and their children remain Hidden by superior). #### Revealing a location with a Hidden ancestor *[Image: Revealing a location with a Hidden ancestor]* When you reveal a location that has a Hidden ancestor, it **doesn't** become Visible itself. Because it still has invisible ancestors, its status changes to Hidden by superior. > **Tip: In short** > > A location can only be Visible when all of its ancestors are Visible as well. ### Visibility mechanics The visibility mechanics are controlled by two flags: Hidden flag and Invisible flag. The Hidden flag informs whether the node has been hidden by a user or not. A raised Invisible flag means that the node is invisible either because it was hidden by a user or by the system. Together, the flags represent the three visibility statuses: | Hidden flag | Invisible flag | Status | | ----------- | -------------- | ------------------------------------------------------------------------------------------------------------------------ | | - | - | The location is visible. | | 1 | 1 | The location is invisible and it was hidden by a user (Hidden). | | - | 1 | The location is invisible and it was hidden by the system because its ancestor is hidden/invisible (Hidden by superior). | > **Note: Note** > > Displaying visible or hidden locations in governed by the [`Visibility` Search Criterion](https://doc.ibexa.co/en/latest/search/criteria_reference/visibility_criterion/index.md) # Content Relations Content items are located in a tree structure through the locations they're placed in. However, content items themselves can also be related to one another. *[Image: Content Relations]* A **Relation** can exist between any two content items in the repository. For example, images are linked to news articles they're used in. Instead of using a fixed set of image attributes, the images are stored as separate content items outside the article. In the system you can find different types of Relations. Content can have Relations on item or on field level. *Relations at field level* are created using one of two special field types: Content relation (single) and Content relations (multiple). These fields allow you to select one or more other content items in the field value, which are linked to these fields. *Relations at content item level* can be of three different types: - *Common Relations* are created between two content items using the public PHP API. - *RichText linked Relations* are created using a field of the RichText type. When an internal link (a link to another location or content item) is placed in a RichText field, the system automatically creates a Relation. The Relation is automatically removed from the system when the link is removed from the content item. - *RichText embedded Relations* also use a RichText field. When an Embed element is placed in a RichText field, the system automatically creates a Relation between the embedded content item and the one with the RichText field. The Relation is automatically removed from the system when the link is removed from the content item. # Content availability The Default content availability flag enables you to control whether content is available when its translation is missing. You can set the flag in content type definition by checking the "Make content available even with missing translations" option. It's automatically applied to any new content item of this Type. *[Image: Default content availability]* A content item with this flag is available in its main language even if it's not translated into the language of the current SiteAccess. Without the flag, a content item isn't available at all if it doesn't have a language version corresponding to the current SiteAccess. > **Note: Note** > > There is currently no way in the back office to edit the Content availability flag for an already published content item. > > To do this via [PHP API](https://doc.ibexa.co/en/latest/content_management/content_api/creating_content/#updating-content), set the [`alwaysAvailable` property](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-ContentMetadataUpdateStruct.html#property_alwaysAvailable) of the Content metadata. The Default availability flag is used for the out-of-the box content types representing content that should always be visible to the user, such as media files or user content items. You can also use it for organizational content types. For example, you can assign the flag to a Blog content type which is intended to contain Blog Posts in multiple languages. If the Blog is in English only, it would not be visible for readers using the Norwegian or German SiteAcceses. However, if you set the default availability flag for the Blog content type, it's displayed to them in English (if it's set as a main language) and enables the users to browse individual posts in other languages. # Taxonomy Taxonomies (**Tags**) allow you to organize content to make it easy for your site users to browse and to deliver content appropriate for them. Taxonomies are classifications of logical relationships between content. In Ibexa DXP you can create many taxonomies, each with many tags. The platform mechanism enables creating any entities with a tree structure and assign them to a content item. Default tag configuration is available in `config/packages/ibexa_taxonomy.yaml` The associated content type is `tag`. ``` ibexa_taxonomy: taxonomies: tags: parent_location_remote_id: taxonomy_tags_folder content_type: tag field_mappings: identifier: identifier parent: parent name: name ``` ## Configuration keys - `ibexa_taxonomies` - section responsible for taxonomy structure where you can [configure other taxonomies](#customize-taxonomy-structure) - `ibexa_taxonomies.tags.parent_location_remote_id` - Remote ID for location where new content items representing tags are created - `ibexa_taxonomies.tags.content_type` - Content type identifier which stands for the tags - `ibexa_taxonomies.tags.field_mappings` - field types map of a content type which taxonomy receives information about the tag from. Three fields are available: `identifier`, `parent` and `name`. The identifiers correspond to field names defined in the content type. The `name` field is used to automatically generate an identifier. ## Customize taxonomy structure You can create other taxonomies than the one predefined in the system, for example a Content category. To do it, first, create a new container to store the new taxonomy's items, for example a folder named "Content categories". Next, under the `ibexa_taxonomy.taxonomies` [key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files) add the following configuration: ``` ibexa_taxonomy: taxonomies: # existing keys content_categories: parent_location_remote_id: content_type: content_category field_mappings: identifier: category_identifier parent: parent_category name: name assigned_content_tab: false ``` Replace `` with the new container's location remote ID. Translate the configuration identifier in the `ibexa_taxonomy` domain by, for example, creating a `translations/ibexa_taxonomy.en.yaml` file containing the following: ``` taxonomy.content_categories: 'Content categories' ``` Then, create a content type with `content_category` identifier and include the following field definitions: - `name` of `ibexa_string` type and required. Use this field, as ``, for content name pattern. - `category_identifier` of `ibexa_string` type and required. - `parent_category` of `ibexa_taxonomy_entry` type and not required. In its Taxonomy drop-down menu, select Content categories (or `taxonomy.content_categories` if no translation has been provided). Finish taxonomy setup by creating a new Content category named Root with identifier `content_categories_root` under the previously created container folder named Content categories. To use this new taxonomy, add an `ibexa_taxonomy_entry_assignement` field to a content type and select Content categories (or `taxonomy.content_categories`) in its Taxonomy drop-down setting. ### Hide Content tab The **Content** tab in taxonomy objects, for example, tags and categories, lists all Content assigned to the current taxonomy. You can hide the **Content** tab in the **Categories** view. In configuration add `assigned_content_tab` with the flag `false` (for other taxonomies this flag is by default set to `true`): ``` ibexa_taxonomy: taxonomies: # existing keys content_categories: parent_location_remote_id: content_type: content_category field_mappings: identifier: category_identifier parent: parent_category name: name assigned_content_tab: false ``` ### Hide menu item By default, for each taxonomy, a menu item is added to the main menu. You can hide this menu item by setting a value of the `register_main_menu` configuration key: ``` ibexa_taxonomy: taxonomies: # existing keys content_categories: # existing keys register_main_menu: false ``` For more information about available functionalities of tags, see [User Documentation](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/taxonomy/taxonomy/). ## Hide delete button on large subtree The **Delete** button can be hidden when a taxonomy entry has many children. By default, the button is hidden when there are 100 children or more. The `delete_subtree_size_limit` configuration is [SiteAccess-aware](https://doc.ibexa.co/en/latest/multisite/siteaccess/siteaccess_aware_configuration/index.md), and can be set per SiteAccess, per SiteAccess group, or globally per default. For example: ``` ibexa: system: default: # or a SiteAccess, or a SiteAccess group taxonomy: admin_ui: delete_subtree_size_limit: 20 ``` ## Remove orphaned content items In some rare case, especially in Ibexa DXP v4.2 and older, when deleting parent of huge subtrees, some taxonomy entries aren't properly deleted, leaving content items that point to a non-existing parent. The command `ibexa:taxonomy:remove-orphaned-content` deletes those orphaned content item. It works on a taxonomy passed as an argument, and has two options that act as a protective measure against deleting data by mistake: - `--dry-run` to list deletable content items, without performing the deletion. - `--force` to effectively delete the orphaned content items. The following example first lists the orphaned content items for taxonomy `tags`, and then deletes them: ``` php bin/console ibexa:taxonomy:remove-orphaned-content tags --dry-run php bin/console ibexa:taxonomy:remove-orphaned-content tags --force ``` ## Taxonomy suggestions Once the feature is [enabled](#enable-taxonomy-suggestions), with taxonomy suggestions, editors can pick from suggestions generated by an AI service based on selected fields like the product's or content item's name and description instead of having to manually browse through taxonomy trees and selecting [product categories](https://doc.ibexa.co/projects/userguide/en/5.0/pim/work_with_product_categories/#assign-product-categories-by-editing-product-details) or [tags](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/create_edit_content_items/#add-taxonomy-entries). Taxonomy suggestions build on existing [AI Actions](https://doc.ibexa.co/en/latest/ai_actions/ai_actions_guide/index.md) functionality. The [`TaxonomyEmbeddingFieldProviderInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Taxonomy-Embedding-TaxonomyEmbeddingFieldProviderInterface.html) service uses an existing taxonomy tree as reference, generating an embedding for each path in the taxonomy tree and storing it in the search index. For performance reasons, embeddings for the taxonomy tree entries are generated only in two cases: - when the search engine is reindexed, for example, right after you enable the feature and run the `ibexa:reindex` command - when an individual taxonomy entry is created or modified, it's embedding is updated When the editor creates or edits a content item or a product, they can request that the application suggests tags or product categories to be associated with the item. When it happens, the `Ibexa\Taxonomy\ActionHandler\TextToTaxonomyActionHandler` requests that an embedding is generated based on selected fields such as, for example, name and description. > **Note: Field selection** > > You select the actual text fields, whose values are used as source for the embedding generation, when you create an [AI action](https://doc.ibexa.co/projects/userguide/en/latest/ai_actions/work_with_ai_actions/#create-ai-actions-that-use-ibexa-connect) that uses the `text-to-taxonomy` handler. The search engine then compares the generated embedding with the taxonomy path embeddings stored in its index. By default, it selects the three best-matching taxonomy paths and presents them to the editor as suggestions. The user can accept the suggestions, reject them, or request a new set of suggestions directly from the user interface. ### Enable Taxonomy suggestions Taxonomy suggestions are built into the product and do not require additional installation. However, before you can enable it, make sure the following prerequisites have been fulfilled: - [Search engine](https://doc.ibexa.co/en/latest/search/search_engines/search_engines/index.md): Taxonomy suggestions require a search engine that supports vector search. The feature has been tested to work with Elasticsearch or Solr 9.8.1+. - [AI Actions](https://doc.ibexa.co/en/latest/ai_actions/ai_actions/index.md): To be able to process embeddings, Taxonomy suggestions require that you have the AI Actions configured to support the default [OpenAI](https://doc.ibexa.co/en/latest/ai_actions/configure_ai_actions/#configure-access-to-openai) or the optional [Google Gemini](https://doc.ibexa.co/en/latest/ai_actions/configure_ai_actions/#install-google-gemini-connector) service. > **Note: Alternative embeddings provider** > > To use Google Gemini as an alternative embeddings provider, you must also modify the default [taxonomy suggestions settings](https://doc.ibexa.co/en/latest/content_management/taxonomy/taxonomy/#change-embeddings-provider-to-google-gemini). #### Enable taxonomy embedding indexing Enable embedding indexing for taxonomy branches by changing the default setting from `false` to `true`. Toggle this setting at any time to enable or disable indexing of taxonomy embeddings. ``` ibexa: system: default: taxonomy: search: index_embeddings: true default_embedding_model: 'text-embedding-ada-002' ``` If you are happy with the default settings, clear the cache and reindex the search engine. ``` php bin/console cache:clear php bin/console ibexa:reindex ``` #### Configure AI action Once you enable the Taxonomy suggestions feature, you must [configure an AI action](https://doc.ibexa.co/projects/userguide/en/5.0/ai_actions/work_with_ai_actions/#create-ai-actions-that-control-taxonomy-suggestions) that handles the generation of embeddings for newly created or edited content items or products. That's where you decide which exact fields from which content type should be used as input for embedding generation, how many suggestions are being presenter to the editor, and so on. After you do it, your users are be able to assign tags and/or product categories by using suggestions provided by an AI engine. ### Customize Taxonomy suggestions You can modify the default behavior of the Taxonomy suggestions model by changing various settings. #### Change default number of suggestions By default, the system returns three suggestions. You can change the default number if needed by altering the following setting: ``` ibexa: taxonomy: text_to_taxonomy: default_suggested_taxonomies_limit: 5 ``` You can also override this setting per AI action by editing its configuration. #### Change default fields parsed when generating suggestions The following setting decides which fields are used to generate suggestions by default. You can change the default setting, if needed. ``` ibexa: system: default: content_type_field_type_groups: configurations: vectorizable_fields: - ibexa_string - ibexa_text - ibexa_richtext ``` This way you can limit field selection to meaningful text fields and avoid unsupported field types. Like in the case of the number of suggestions, you can override this setting per AI action by editing its configuration. > **Tip: Tip** > > When selecting the input data for embedding creation, it's recommended to include only the essential information and limit the number of tokens sent. Otherwise, the embedding models can generate values that don't correspond closely to the actual meaning of the input. ### Change embedding generation models or embedding provider By default, the system comes with a set of OpenAI models that can be used for embedding generation. The following example shows these models listed in system configuration, together with a setting that controls what model is used when the editor requests taxonomy suggestions for an item. Also, here is where you can change the name of the model used by the provider, the embedding's dimensions, and other settings. ``` ibexa: system: default: embedding_models: text-embedding-3-small: name: 'text-embedding-3-small' dimensions: 1536 field_suffix: '3small' embedding_provider: 'ibexa_openai' text-embedding-3-large: name: 'text-embedding-3-large' dimensions: 3072 field_suffix: '3large' embedding_provider: 'ibexa_openai' text-embedding-ada-002: name: 'text-embedding-ada-002' dimensions: 1536 field_suffix: 'ada002' embedding_provider: 'ibexa_openai' default_embedding_model: 'text-embedding-ada-002' ``` > **Warning: Change both embedding generation models** > > When you change the default suggestions generation model, ensure that you update the `ibexa.system.default.taxonomy.search.default_embedding_model` setting that is used for taxonomy indexing purposes. Otherwise the taxonomy suggestions feature fails to find matching entries. #### Change embeddings provider to Google Gemini (LTS Update) Once you have installed and configured the [Google Gemini connector](https://doc.ibexa.co/en/latest/ai_actions/configure_ai_actions/#install-google-gemini-connector), you can modify the default configuration to use the `ibexa_gemini` embedding provider and one of the [supported models](https://ai.google.dev/gemini-api/docs/embeddings): ``` ibexa: system: default: embedding_models: gemini_embedding_001_1536: name: 'gemini-embedding-001' dimensions: 1536 field_suffix: 'gemini_embedding_001_1536_dv' embedding_provider: 'ibexa_gemini' gemini_embedding_001_3072: name: 'gemini-embedding-001' dimensions: 3072 field_suffix: 'gemini_embedding_001_3072_dv' embedding_provider: 'ibexa_gemini' default_embedding_model: 'gemini_embedding_001_1536' # ... taxonomy: search: index_embeddings: true default_embedding_model: 'gemini_embedding_001_1536' ``` After you make the change: - Update the [Solr schema](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_search/#configuring-solr) or [Elasticsearch mappings](https://doc.ibexa.co/en/latest/search/search_engines/elasticsearch/configure_elasticsearch/#fine-tune-the-search-results) by adding dynamic field definitions. Ensure that they match the dimensions (for example, 1536 or 3072) and suffixes that you defined above - Clear the cache and reindex the search engine ### Extending Taxonomy suggestions You can extend the feature by replacing the default code by exploring one of the following ideas. #### Replace the embedding provider By default, the system uses the `ibexa_openai` connector. You can add your own embedding provider if needed. To do it: - Implement the [`EmbeddingProviderInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Search-Embedding-EmbeddingProviderInterface.html) - Register the service with the `ibexa.embedding_provider` tag #### Extend the AI action form You can extend the `TextToTaxonomyOptionsType` AI action form by inheriting from `Ibexa\Bundle\Taxonomy\Form\Type\AbstractActionConfigurationOptions`. # Taxonomy API To manage taxonomies, use [`TaxonomyServiceInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Taxonomy-Service-TaxonomyServiceInterface.html). ## Getting taxonomy entries To get a single taxonomy entry, you can use `TaxonomyServiceInterface::loadEntryById()`, and provide it with the numerical entry ID. Or pass entry identifier (with optionally a taxonomy identifier), and use `TaxonomyServiceInterface::loadEntryByIdentifier()`: ``` $entry = $this->taxonomyService->loadEntryByIdentifier('desks'); $output->writeln($entry->name . ' with parent ' . $entry->parent->name); ``` > **Note: Note** > > A taxonomy entry identifier is unique per taxonomy. If you have [several taxonomies](https://doc.ibexa.co/en/latest/content_management/taxonomy/taxonomy/#customize-taxonomy-structure), you can increase code readability by always passing the taxonomy identifier even when it's the default one. The default taxonomy is `tags` if it exists, else the first configured taxonomy (see `\Ibexa\Taxonomy\Service\TaxonomyConfiguration::getDefaultTaxonomyName` for details). > > ``` > $springs[] = $this->taxonomyService->loadEntryByIdentifier('spring', 'tags'); > $springs[] = $this->taxonomyService->loadEntryByIdentifier('spring', 'events'); > $springs[] = $this->taxonomyService->loadEntryByIdentifier('spring', 'devices'); > ``` You can also get a taxonomy entry from the ID of its underlying content item, by using `TaxonomyServiceInterface::loadEntryByContentId()`. To get the root (main) entry of a given taxonomy, use `TaxonomyServiceInterface::loadRootEntry()` and provide it with the taxonomy name. To get all entries in a taxonomy, use `TaxonomyServiceInterface::loadAllEntries()`, provide it with the taxonomy identifier, and optionally specify the limit of results and their offset. The default taxonomy identifier is given by `TaxonomyConfiguration::getDefaultTaxonomyName` and is `'tags'` on a fresh installation. The default limit is 30. ``` $allEntries = $this->taxonomyService->loadAllEntries(null, 50); ``` To see how many entries is there, use `TaxonomyServiceInterface::countAllEntries()` with optionally a taxonomy identifier. To get all children of a specific taxonomy entry, use `TaxonomyServiceInterface::loadEntryChildren()`, provide it with the entry object, and optionally specify the limit of results and their offset. The default limit is 30: ``` $entryChildren = $this->taxonomyService->loadEntryChildren($entry, 10); foreach ($entryChildren as $child) { $output->writeln($child->name); } ``` ## Managing taxonomy entries You can move a taxonomy entry to a different parent by using `TaxonomyServiceInterface::moveEntry()`. Provide the method with two objects: the entry that you want to move and the new parent entry: ``` $entryToMove = $this->taxonomyService->loadEntryByIdentifier('standing_desks'); $newParent = $this->taxonomyService->loadEntryByIdentifier('desks'); $this->taxonomyService->moveEntry($entryToMove, $newParent); ``` You can also move a taxonomy entry by passing its target sibling entry to `TaxonomyServiceInterface::moveEntry()`. The method takes as parameters the entry you want to move, the future sibling, and a `position` parameter, which is either `TaxonomyServiceInterface::MOVE_POSITION_NEXT` or `TaxonomyServiceInterface::MOVE_POSITION_PREV`: ``` $sibling = $this->taxonomyService->loadEntryByIdentifier('school_desks'); $this->taxonomyService->moveEntryRelativeToSibling($entryToMove, $sibling, TaxonomyServiceInterface::MOVE_POSITION_PREV); ``` > **Note: Note** > > Taxonomy entry management functions triggers events you can listen to. For more information, see [Taxonomy events](https://doc.ibexa.co/en/latest/api/event_reference/taxonomy_events/index.md). ## Search You can search for content based on its taxonomy entry assignments by using the standard [`SearchService`](https://doc.ibexa.co/en/latest/search/search_api/index.md) with taxonomy-specific Search Criteria: | Criterion | Description | | ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | | [TaxonomyEntryId](https://doc.ibexa.co/en/latest/search/criteria_reference/taxonomy_entry_id/index.md) | Find content assigned to a specific taxonomy entry | | [TaxonomyNoEntries](https://doc.ibexa.co/en/latest/search/criteria_reference/taxonomy_no_entries/index.md) | Find content that has no entries assigned from a given taxonomy | | [TaxonomySubtree](https://doc.ibexa.co/en/latest/search/criteria_reference/taxonomy_subtree/index.md) | Find content assigned to a taxonomy entry or any of its descendants | You can also use the [TaxonomyEntryId Aggregation](https://doc.ibexa.co/en/latest/search/aggregation_reference/taxonomyentryid_aggregation/index.md) to count content items per taxonomy entry. # Images Images are an integral part of any website. They can serve as decoration and convey information. In Ibexa DXP, you can reuse them, normalize their file names, generate different size variations, resize images programmatically, or even define placeholders for missing ones. ## Images from DAM systems If your installation is connected to a DAM system, you can use images directly from a DAM system in your content. Specific [DAM configuration](https://doc.ibexa.co/en/latest/content_management/images/add_image_asset_from_dam/#dam-configuration) depends on the system that the installation uses. ## Reuse images You can store images in the media library as independent content items of a generic Image [content type](https://doc.ibexa.co/en/latest/administration/content_organization/content_types/index.md) to reuse them across the system. You do this by uploading images to an [ImageAsset](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/imageassetfield/index.md) field type. For an ImageAsset field to be reused, you must publish it. Only then is notification triggered, which states that an image has been published under the location and can now be reused. After you establish a media library, you can create [Relations](https://doc.ibexa.co/en/latest/content_management/content_relations/index.md) between the image content item and the main content item that uses it. ## Normalizing image file names If you use image files with unprintable UTF-8 characters in file names, you may come across a problem with images not displaying. Run the following command to normalize image file names: ``` php bin/console ibexa:images:normalize-paths ``` Next, clear the cache: ``` php bin/console cache:clear ``` and run the following: ``` php bin/console liip:imagine:cache:remove ``` ## Configuring image variations With [image variations](https://doc.ibexa.co/en/latest/templating/image_variations/index.md) (image aliases) you can define and use different versions of the same image. You generate variations based on [filters](https://doc.ibexa.co/en/latest/templating/image_variations/#available-variation-filters) that modify aspects such as size and proportions, quality or effects. Image variations are generated with [LiipImagineBundle](https://github.com/liip/LiipImagineBundle), by using the underlying [Imagine library](https://imagine.readthedocs.io/en/latest/).  The LiipImagineBundle bundle supports GD (default), Imagick or Gmagick PHP extensions, and enables you to define flexible filters in PHP.  Image files are stored by using the `IOService,` and are completely independent from the Image field type. They're generated only once and cleared on demand, for example, on content removal). LiipImagineBundle only works on image blobs, so no command line tool is needed. For more information, see the [bundle's documentation](https://symfony.com/bundles/LiipImagineBundle/current/configuration.html). > **Caution: Code injection in images** > > Images must be treated like any other user-submitted data - as potentially malicious. > > - EXIF metadata of an image may contain for example, HTML, JavaScript, or PHP code. Ibexa DXP itself doesn't parse EXIF metadata, but third-party bundles must be secured against this eventuality. Make sure that metadata is properly escaped before use. > - Images may contain specially crafted flaws that exploit vulnerabilities in common image libraries like GD or Imagick, leading to code execution. It's important to keep these libraries up to date with security updates. ### Image URL resolution You can use LiipImagine's `liip:imagine:cache:resolve` command to resolve the path to image variations that are generated from the original image, with one or more paths as arguments. Paths to repository images must be relative to the `var//storage/images` directory, for example: `7/4/2/0/247-1-eng-GB/test.jpg`. For more information, see [LiipImagineBundle documentation](https://symfony.com/bundles/LiipImagineBundle/current/basic-usage.html#resolve-with-the-console). ## Resizing images You can resize all original images of a chosen content type with the following command. ``` php bin/console ibexa:images:resize-original -f ``` You must provide the command with: - identifier of the image content type - identifier of the field that you want to affect - name of the image variation to apply to the images For example: ``` php bin/console ibexa:images:resize-original image photo -f small_image ``` You can also pass two additional parameters: - `iteration-count` is the number of images to be recreated in a single iteration, to reduce memory use. The default value is `25`. - `user` is the identifier of a User with proper permission who performs the operation (`read`, `versionread`, `edit` and `publish`). The default value is `admin`. > **Caution: Caution** > > The `resize-original` command publishes a new version of each content item it modifies. ## Generating placeholder images With a placeholder generator you can download or generate placeholder images for any missing image. It proves useful when you're working on an existing database and are unable to download uploaded images to your local development environment, due to, for example, a large size of files. If the original image cannot be resolved, the `PlaceholderAliasGenerator::getVariation` method generates a placeholder by delegating it to the implementation of the [PlaceholderProvider](https://github.com/ibexa/core/blob/main/src/bundle/Core/Imagine/PlaceholderProvider.php) interface, and saves it under the original path. In Ibexa DXP, there are two implementations of the `PlaceholderProvider` interface: - [GenericProvider](#genericprovider) - [RemoteProvider](#remoteprovider) ### GenericProvider The [`GenericProvider`](https://github.com/ibexa/core/blob/main/src/bundle/Core/Imagine/PlaceholderProvider.php) package generates placeholders with basic information about the original image (see [example 1](#configuration-examples)). *[Image: Placeholder image GenericProvider]* *[Image: Placeholder GenericProvider]* | Option | Default value | Description | Required? | | ---------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | --------- | | fontpath | n/a | Path to the font file (\*.ttf). | Yes | | text | "IMAGE PLACEHOLDER %width%x%height%\\n(%id%)" | Text which is displayed in the image placeholder. %width%, %height%, %id% in it's replaced with width, height and ID of the original image. | | | fontsize | 20 | Size of the font in the image placeholder. | | | foreground | #000000 | Foreground color of the placeholder. | | | secondary | #CCCCCC | Secondary color of the placeholder. | | | background | #EEEEEE | Background color of the placeholder. | | ### RemoteProvider With the [`RemoteProvider`](https://github.com/ibexa/core/blob/main/src/bundle/Core/Imagine/PlaceholderProvider/RemoteProvider.php) you can download placeholders from: - remote sources, for example, (see [example 2](#configuration-examples)) - live version of a site (see [example 3](#configuration-examples)) *[Image: Placeholder RemoteProvider - placekitten.com]* | Option | Default value | Description | | ----------- | ------------- | ------------------------------------------------------------------------------------------------------ | | url_pattern | '' | URL pattern. %width%, %height%, %id% in it's replaced with width, height and ID of the original image. | | timeout | 5 | Period of time before timeout, measured in seconds. | ### Semantic configuration Placeholder generation can be configured for each [`binary_handler`](https://doc.ibexa.co/en/latest/content_management/file_management/file_management/#handling-binary-files) under the `ibexa.image_placeholder` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa: # ... image_placeholder: : provider: options: ``` If there is no configuration assigned to the `binary_handler`, the placeholder generation is disabled. ##### Configuration examples: **Example 1 - placeholders with basic information about original image** ``` ibexa: image_placeholder: default: provider: generic options: fontpath: '%kernel.project_dir%/src/Resources/font/font.ttf' background: '#EEEEEE' foreground: '#FF0000' text: 'MISSING IMAGE %%width%%x%%height%%' ``` **Example 2 - placeholders from remote source** ``` ibexa: image_placeholder: default: provider: remote options: url_pattern: 'https://placekitten.com/%%width%%/%%height%%' ``` **Example 3 - placeholders from live version of a site** ``` ibexa: image_placeholder: default: provider: remote options: url_pattern: 'http://example.com/var/site/storage/%%id%%' ``` ## Support for SVG images You cannot store SVG images in Ibexa DXP by using the Image or ImageAsset field type. However, you can work things around by relying on the File field type and implementing a custom extension that lets you display and download files in your templates. > **Caution: Caution** > > SVG images may contain JavaScript, so they may introduce XSS or other security vulnerabilities. Make sure end users aren't allowed to upload SVG images, and be restrictive about which editors are allowed to do so. First, enable adding SVG files to content by removing them from the blacklist of allowed MIME types. To do it, overwrite `ibexa.site_access.config.default.io.file_storage.file_type_blacklist` defined in `Core/Resources/config/default_settings.yml` so that `svg` is removed from the blacklist. You can do it per SiteAccess or SiteAccess group by using [SiteAccess-aware configuration](https://doc.ibexa.co/en/latest/multisite/siteaccess/siteaccess_aware_configuration/index.md). Then, add a download route to the `config/routes.yaml` file: ``` app.svg_download: path: /asset/download/{contentId}/{fieldIdentifier}/{filename} defaults: { _controller: App\Controller\SvgController::downloadSvgAction } ``` It points to a custom controller that handles the downloading of the SVG file. The controller's definition (that you place in the `config/services.yaml` file under `services` key) and implementation are as follows: ``` services: # ... App\Controller\SvgController: public: true arguments: - '@ibexa.api.service.content' - '@ibexa.field_type.ibexa_binaryfile.io_service' - '@Ibexa\Core\Helper\TranslationHelper' ``` ``` query->has('version')) { $version = (int)$request->query->get('version'); } $content = $this->contentService->loadContent($contentId, null, $version); $language = $request->query->has('inLanguage') ? $request->query->get('inLanguage') : null; $field = $this->translationHelper->getTranslatedField($content, $fieldIdentifier, $language); if (!$field instanceof Field) { throw new InvalidArgumentException( sprintf( "%s field not present in content %d '%s'", $fieldIdentifier, $content->contentInfo->id, $content->contentInfo->name ) ); } $binaryFile = $this->ioService->loadBinaryFile($field->value->id); $response = new Response($this->ioService->getFileContents($binaryFile)); $disposition = $response->headers->makeDisposition( ResponseHeaderBag::DISPOSITION_INLINE, $filename ); $response->headers->set('Content-Disposition', $disposition); $response->headers->set('Content-Type', self::CONTENT_TYPE_HEADER); return $response; } } ``` To be able to use a proper link in your templates, you also need a dedicated Twig extension: ``` generateLink(...)), ]; } public function generateLink(int $contentId, string $fieldIdentifier, string $filename): string { return $this->router->generate('app.svg_download', [ 'contentId' => $contentId, 'fieldIdentifier' => $fieldIdentifier, 'filename' => $filename, ]); } } ``` Now you can load SVG files in your templates by using generated links and a newly created Twig helper: ``` {% set svgField = ibexa_field(content, 'file') %} ``` ## Image optimization JPEG images are optimized using the ImageMagic library, which is available out of the box. If you use other formats, such a PNG, SVG, GIF, or WEBP, and you use the Image Editor, to prevent images increasing in size when you modify them in the editor, you need to install additional image handling libraries. | Image format | Library | | ------------ | ---------------------------- | | JPEG | JpegOptim | | PNG | Either OptiPNG or Pngquant 2 | | SVG | SVGO 1 | | GIF | Gifsicle | | WEBP | cwebp | Install these libraries using your package manager, for example: ``` sudo apt-get install optipng ``` ### Customizing image optimizers When the Image Editor saves a modified image, the system dispatches the [`ConfigureImageOptimizersEvent`](https://doc.ibexa.co/en/latest/api/event_reference/other_events/#image-editor) event before running the optimizer chain. You can listen to this event to customize the list of image optimizers at runtime. The following example shows how to remove the Pngquant optimizer to prevent grayscale conversion of low-saturation PNG images: ``` 'onConfigureOptimizers', ]; } public function onConfigureOptimizers(ConfigureImageOptimizersEvent $event): void { $event->removeOptimizer(Pngquant::class); } } ``` ## Embedding images in Rich Text The [RichText](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/richtextfield/index.md) field allows you to embed other content items within the field. Content items that are identified as images are rendered in the Rich Text field by using a dedicated template. You can determine content types that are treated as images and rendered. You do this by overriding the `ibexa.content_view.image_embed_content_types_identifiers` parameter, for example: ``` parameters: ibexa.content_view.image_embed_content_types_identifiers: [image, photo, banner] ``` You can set the template that is used when rendering embedded images in the `ibexa.default_view_templates.content.embed_image` container parameter: ``` parameters: ibexa.default_view_templates.content.embed_image: '@ibexadesign/content/view/embed/image.html.twig' ``` # Configure Image Editor When a content item contains fields of the [ibexa_image](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/imageassetfield/index.md) type, users can perform basic image editing functions with the Image Editor. For more information, see [User Documentation](https://doc.ibexa.co/projects/userguide/en/5.0/image_management/edit_images/). > **Note: Note** > > The Image Editor doesn't support images that come from a Digital Asset Management (DAM) system. > **Note: Note** > > If you intend to modify images in formats other than JPEG in image editor, consider [adding a library to optimize them](https://doc.ibexa.co/en/latest/content_management/images/images/#image-optimization). ## Configuration You can modify the default settings to change the appearance or behavior of the Image Editor. You can also expand the default set of parameters to create buttons that may be required by custom features that you add by extending the Image Editor, for example, to enable changes to the color palette of an image. To do this, under the `ibexa.system..image_editor` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files) add a settings tree similar to the following example. The settings tree can contain one or more action groups. You can control the order of actions within a group by setting the `priority` parameter. You can also toggle the visibility of actions within the user interface. Image Editor settings are [SiteAccess-aware](https://doc.ibexa.co/en/latest/administration/configuration/dynamic_configuration/index.md). The following example sets the aspect ratio values and label names for buttons used by the Crop feature. ``` ibexa: system: default: image_editor: action_groups: default: id: default label: Default actions: crop: id: crop priority: 1 visible: true buttons: 1-1: label: 1:1 ratio: x: 1 y: 1 3-4: label: 3:4 ratio: x: 3 y: 4 4-3: label: 4:3 ratio: x: 4 y: 3 16-9: label: 16:9 ratio: x: 16 y: 9 custom: label: Custom ``` ### Image quality You can configure the quality of the images modified in the Image Editor with the following configuration. The setting accepts values between 0 and 1, which corresponds to the compression level, with 0 being the strongest compression. The default quality is 0.92: ``` ibexa: system: default: image_editor: image_quality: 0.8 ``` ### Additional information Each image can be accompanied by additional information that isn't visible to the user. By default, additional information stores the coordinates of the [focal point](https://doc.ibexa.co/projects/userguide/en/5.0/image_management/edit_images/#focal-point), but you can use this extension point to pass various parameters of custom features that you add by extending the Image Editor. To modify the value of additional information programmatically, you can set a value of the `Image` field by using the PHP API, for example: ``` new FieldValue([ 'data' => [ 'width' => '100', 'height' => '200', 'alternativeText' => 'test', 'mime' => 'image/png', 'id' => 1, 'fileName' => 'image.png', 'additionalData' => [ 'focalPointX' => 50, 'focalPointY' => 100, 'author' => 'John Smith', ], ], ]), ``` # Extend Image Editor With the Image Editor, users can do basic image modifications. You can configure the Image Editor's [default appearance or behavior](https://doc.ibexa.co/en/latest/content_management/images/configure_image_editor/index.md). You can also extend it by adding custom features. The following example shows how to extend the Image Editor by adding a button that draws a dot at a random location on the image. ## Create the JavaScript component file In `assets/random_dot/`, create the `random-dot.js` file with the following code of the React component: ``` import React, { useContext } from 'react'; import PropTypes from 'prop-types'; const { ibexa } = window; const IDENTIFIER = 'dot'; const Dot = () => { return (
    ); }; Dot.propTypes = {}; Dot.defaultProps = {}; export default Dot; ibexa.addConfig( 'imageEditor.actions.dot', // The ID ("dot") must match the one from the configuration yaml file { label: 'Dot', component: Dot, icon: ibexa.helpers.icon.getIconPath('form-radio'), // Path to an icon that will be displayed in the UI identifier: IDENTIFIER, // The identifier must match the one from the configuration yaml file }, true, ); ``` The code doesn't perform any action yet, you add the action in the following steps. ## Add configuration Configure the new Image Editor action under the `ibexa.system..image_editor` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa: system: default: image_editor: action_groups: default: id: default label: Default actions: dot: id: dot priority: 50 ``` ## Add entry to the Webpack configuration Once you create and configure the React component, you must add an entry to [the Webpack configuration](https://doc.ibexa.co/en/latest/tutorials/beginner_tutorial/3_customize_the_front_page/#configuring-webpack). In the root directory of your project, modify the `webpack.config.js` file by adding the following code: ``` const ibexaConfigManager = require('./ibexa.webpack.config.manager.js'); //... ibexaConfigManager.add({ ibexaConfig, entryName: 'ibexa-admin-ui-layout-js', newItems: [ path.resolve(__dirname, './assets/random_dot/random-dot.js'), ], }); ``` At this point you should be able to see a new button in the Image Editor's UI. > **Tip: Tip** > > Before you restart Ibexa DXP, run `php bin/console cache:clear` and `yarn encore ` to regenerate the assets. ## Expand the React component The button that you created above doesn't initiate any action yet. You must modify the JavaScript component to add a function to the button. ### Contexts When you create a React-based extension of the Image Editor, you can use a number of contexts that have the following functions: - CanvasContext - stores a canvas that displays the image, on which you can modify the image - ImageHistoryContext - stores the image history used by the Undo/Redo feature - AdditionalDataContext - stores additional data that is attached to the image, for example, focal point coordinates - TechnicalCanvasContext - stores a canvas, which you can use to draw elements that help modify the image, for example, a crop area or a grid, without interrupting with the actual image The last context is not used in this example. ### Draw a dot Modify the `random-dot.js` file by creating a function that uses the canvas context to draw a random dot on the image: ``` const drawDot = () => { const ctx = canvas.current.getContext('2d'); const positionX = Math.random() * canvas.current.width; const positionY = Math.random() * canvas.current.height; ctx.save(); ctx.fillStyle = '#ae1164'; ctx.beginPath(); ctx.arc(positionX, positionY, 20, 0, Math.PI * 2, true); ctx.fill(); ctx.restore(); saveInHistory(); }; ``` ### Store changes in history Create another function that uses the history context to store changes, so that users can undo their edits: ``` const saveInHistory = () => { const newImage = new Image(); newImage.onload = () => { dispatchImageHistoryAction({ type: 'ADD_TO_HISTORY', image: newImage, additionalData }); }; newImage.src = canvas.current.toDataURL(); }; ``` Complete component code ``` import React, { useContext } from 'react'; import PropTypes from 'prop-types'; import { CanvasContext, ImageHistoryContext, AdditionalDataContext, } from '../../vendor/ibexa/image-editor/src/bundle/ui-dev/src/modules/image-editor/image.editor.modules'; const { ibexa } = window; const IDENTIFIER = 'dot'; const Dot = () => { const [canvas, setCanvas] = useContext(CanvasContext); const [imageHistory, dispatchImageHistoryAction] = useContext(ImageHistoryContext); const [additionalData, setAdditionalData] = useContext(AdditionalDataContext); const saveInHistory = () => { const newImage = new Image(); newImage.onload = () => { dispatchImageHistoryAction({ type: 'ADD_TO_HISTORY', image: newImage, additionalData }); }; newImage.src = canvas.current.toDataURL(); }; const drawDot = () => { const ctx = canvas.current.getContext('2d'); const positionX = Math.random() * canvas.current.width; const positionY = Math.random() * canvas.current.height; ctx.save(); ctx.fillStyle = '#ae1164'; ctx.beginPath(); ctx.arc(positionX, positionY, 20, 0, Math.PI * 2, true); ctx.fill(); ctx.restore(); saveInHistory(); }; return (
    ); }; Dot.propTypes = {}; Dot.defaultProps = {}; export default Dot; ibexa.addConfig( 'imageEditor.actions.dot', { label: 'Dot', component: Dot, icon: ibexa.helpers.icon.getIconPath('form-radio'), identifier: IDENTIFIER, }, true, ); ``` Clear the cache and rebuild assets with the following commands: ``` php bin/console cache:clear yarn encore dev ``` At this point you should be able to draw a random dot by clicking a button in the Image Editor's UI. # Add Image Asset from Digital Asset Management With the Digital Asset Management (DAM) system connector you can use assets such as images directly from the DAM in your content. ## DAM configuration You can configure a connection with a Digital Asset Management (DAM) system under the `ibexa.system..content.dam` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files). ``` ibexa: system: default: content: dam: [ dam_name ] ``` The configuration for each connector depends on the requirements of the specific DAM system. You can use the provided example DAM connector for [Unsplash](https://unsplash.com/), or [extend DAM support by creating a connector of your choice](#extend-dam-support-by-adding-custom-connector). To add the Unsplash connector to your system, add the `ibexa/connector-unsplash` bundle to your installation. ## Add Image Asset in Page Builder (Experience) (Commerce) To add Image Assets directly in the Page Builder, you can do it by using the Embed block. The example below shows how to add images from [Unsplash](https://unsplash.com/). First, in `templates/themes/standard/embed/`, create a custom template `dam.html.twig`: ``` {% set dam_image = ibexa_field_value(content, 'image') %} {% if dam_image.source is not null %} {% set transformation = ibexa_dam_image_transformation(dam_image.source, '770px') %} {% set asset = ibexa_dam_asset(dam_image.destinationContentId, dam_image.source, transformation) %} {% set image_uri = asset.assetUri.path %} {% endif %} ``` The `770px` parameter in the template above is used to render the DAM image. It's the `unsplash` specific image variation and must be defined separately. Next, in `config/packages/ibexa.yaml`, set the `dam.html.twig` template for the `embed` view type that is matched for the content type, which you created for DAM images. For more information about displaying content, see [Content rendering](https://doc.ibexa.co/en/latest/templating/render_content/render_content/index.md). ``` ibexa: system: site: content_view: embed: image_dam: template: '@ibexadesign/embed/dam.html.twig' match: Identifier\ContentType: ``` In your [configuration file](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files) add the following configuration: ``` dam_unsplash: application_id: utm_source: variations: 770px: fm: jpg q: 80 w: 770 fit: max ``` You can customize the parameters according to your needs. For more information about supported parameters, see the [Unsplash documentation](https://unsplash.com/documentation#dynamically-resizable-images). In the back office, go to **Admin** > **Content types**. In the **Content** group, create a content type for DAM images, which includes the ImageAsset field. Now, when you use the Embed block in the Page Builder, you should see a DAM Image. For more information about block customization (defined templates, variations), see [Create custom block](https://doc.ibexa.co/en/latest/tutorials/page_and_form_tutorial/4_create_a_custom_block/index.md). ## Extend DAM support by adding custom connector To extend the DAM support built into Ibexa DXP, you must create a custom handler and transformation factory. > **Note: Wikimedia Commons licensing** > > Before you use Wikimedia Commons assets in a production environment, ensure that you comply with their [license requirements](https://commons.wikimedia.org/wiki/Commons:Reusing_content_outside_Wikimedia#How_to_comply_with_a_file's_license_requirements). ### Create DAM handler This class handles searching through Wikimedia Commons for images and fetching image assets. In `src/Connector/Dam/Handler` folder, create the `WikimediaCommonsHandler.php` file that resembles the following example, which implements [`search()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connector-Dam-Handler-Handler.html#method_search) to query the server and [`fetchAsset()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connector-Dam-Handler-Handler.html#method_fetchAsset) to return asset objects: ``` getPhrase()) . '&sroffset=' . $offset . '&srlimit=' . $limit ; $opts = [ 'http' => [ 'method' => 'GET', 'header' => [ 'User-Agent: ' . self::USER_AGENT, ], ], ]; $jsonResponse = file_get_contents($searchUrl, false, stream_context_create($opts)); if ($jsonResponse === false) { return new AssetSearchResult(0, new AssetCollection([])); } $response = json_decode($jsonResponse, true); if (!isset($response['query']['search'])) { return new AssetSearchResult(0, new AssetCollection([])); } $assets = []; foreach ($response['query']['search'] as $result) { $identifier = str_replace('File:', '', $result['title']); $assets[] = $this->fetchAsset($identifier); } return new AssetSearchResult( (int) ($response['query']['searchinfo']['totalhits'] ?? 0), new AssetCollection($assets) ); } public function fetchAsset(string $id): Asset { $metadataUrl = 'https://commons.wikimedia.org/w/api.php?action=query&prop=imageinfo&iiprop=extmetadata&format=json' . '&titles=File%3a' . urlencode($id) ; $opts = [ 'http' => [ 'method' => 'GET', 'header' => [ 'User-Agent: ' . self::USER_AGENT, ], ], ]; $jsonResponse = file_get_contents($metadataUrl, false, stream_context_create($opts)); if ($jsonResponse === false) { throw new \RuntimeException('Couldn\'t retrieve asset metadata'); } $response = json_decode($jsonResponse, true); if (!isset($response['query']['pages'])) { throw new \RuntimeException('Couldn\'t parse asset metadata'); } $pageData = array_values($response['query']['pages'])[0] ?? null; if (!isset($pageData['imageinfo'][0]['extmetadata'])) { throw new \RuntimeException('Couldn\'t parse image asset metadata'); } $imageInfo = $pageData['imageinfo'][0]['extmetadata']; return new Asset( new AssetIdentifier($id), new AssetSource('commons'), new AssetUri('https://commons.wikimedia.org/w/index.php?title=Special:Redirect/file/' . urlencode($id)), new AssetMetadata([ 'page_url' => "https://commons.wikimedia.org/wiki/File:$id", 'author' => $imageInfo['Artist']['value'] ?? null, 'license' => $imageInfo['LicenseShortName']['value'] ?? null, 'license_url' => $imageInfo['LicenseUrl']['value'] ?? null, ]) ); } } ``` Then, in `config/services.yaml`, register the handler as a service: ``` App\Connector\Dam\Handler\WikimediaCommonsHandler: tags: - { name: 'ibexa.platform.connector.dam.handler', source: 'commons' } ``` The `source` parameter passed in the tag is an identifier of this new DAM connector and is used in other places to glue elements together. ### Create transformation factory The transformation factory maps Ibexa DXP's image variations to corresponding variations from Wikimedia Commons. In `src/Connector/Dam/Transformation` folder, create the `WikimediaCommonsTransformationFactory.php` file that resembles the following example, which implements the [`TransformationFactory` interface](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connector-Dam-Variation-TransformationFactory.html): ``` $transformationParameters */ public function build(?string $transformationName = null, array $transformationParameters = []): Transformation { if (null === $transformationName) { return new Transformation(null, array_map(strval(...), $transformationParameters)); } $transformations = $this->buildAll(); if (array_key_exists($transformationName, $transformations)) { return $transformations[$transformationName]; } throw new \InvalidArgumentException(sprintf('Unknown transformation "%s".', $transformationName)); } public function buildAll(): array { return [ 'reference' => new Transformation('reference', []), 'tiny' => new Transformation('tiny', ['width' => '30']), 'small' => new Transformation('small', ['width' => '100']), 'medium' => new Transformation('medium', ['width' => '200']), 'large' => new Transformation('large', ['width' => '300']), ]; } } ``` Then register the transformation factory as a service: ``` App\Connector\Dam\Transformation\WikimediaCommonsTransformationFactory: tags: - { name: 'ibexa.platform.connector.dam.transformation_factory', source: 'commons' } ``` ### Register variations generator The variation generator applies map parameters coming from the transformation factory to build a fetch request to the DAM. The solution uses the built-in `URLBasedVariationGenerator` class, which adds all the map elements as query parameters to the request. For example, for an asset with the ID `Ibexa_Logo.svg`, the handler generates the Asset with [`AssetUri's URL`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connector-Dam-AssetUri.html#method_getPath) equal to: `https://commons.wikimedia.org/w/index.php?title=Special:Redirect/file/Ibexa_Logo.svg` When the user requests a specific variation of the image, for example, "large", the variation generator modifies the URL and returns it in the following form: `https://commons.wikimedia.org/w/index.php?title=Special:Redirect/file/Ibexa_Logo.svg&width=300` For this to happen, register the variations generator as a service available for the custom `commons` connector: ``` commons_asset_variation_generator: class: Ibexa\Connector\Dam\Variation\URLBasedVariationGenerator tags: - { name: 'ibexa.platform.connector.dam.variation_generator', source: 'commons' } ``` ### Configure tab for "Select from DAM" modal To enable selecting an image from the DAM system, a modal window pops up with tabs and panels that contain different search interfaces. In this example, the search only uses the main text input. The tab and its corresponding panel are a service created by combining existing components, like in the case of other [back office tabs](https://doc.ibexa.co/en/latest/administration/back_office/back_office_tabs/back_office_tabs/index.md). The `commons_search_tab` service uses the `GenericSearchTab` class as a base, and the `GenericSearchType` form for search input. It is linked to the `commons` DAM source and uses the identifier `commons`. The DAM search tab is registered in the `connector-dam-search` [tab group](https://doc.ibexa.co/en/latest/administration/back_office/back_office_tabs/back_office_tabs/#tab-groups) using the `ibexa.admin_ui.tab` tag. ``` commons_search_tab: class: Ibexa\Connector\Dam\View\Search\Tab\GenericSearchTab public: false arguments: $identifier: 'commons' $source: 'commons' $name: 'Wikimedia Commons' $searchFormType: 'Ibexa\Connector\Dam\Form\Search\GenericSearchType' $formFactory: '@form.factory' tags: - { name: 'ibexa.admin_ui.tab', group: 'connector-dam-search' } ``` ### Create Twig template The template defines how images that come from Wikimedia Commons are displayed. In `templates/themes/standard/`, add the `commons_asset_view.html.twig` file that resembles the following example: ``` {% extends '@ibexadesign/ui/field_type/image_asset_view.html.twig' %} {% block asset_preview %} {{ parent() }}
    Image {% if asset.assetMetadata.author %} by {{ asset.assetMetadata.author|striptags }}{% endif %} {% if asset.assetMetadata.license and asset.assetMetadata.license_url %} under {{ asset.assetMetadata.license }} {% endif %}.
    {% endblock %} ``` Then, register the template and a fallback template in configuration files. Replace `` with an [appropriate value](https://doc.ibexa.co/en/latest/multisite/siteaccess/siteaccess_aware_configuration/index.md) that designates the SiteAccess or SiteAccess group, for example, `default` to use the template everywhere, including the back office: ``` parameters: ibexa.site_access.config..image_asset_view_defaults: full: commons: template: '@@ibexadesign/commons_asset_view.html.twig' match: SourceBasedViewMatcher: commons default: template: '@@ibexadesign/ui/field_type/image_asset_view.html.twig' match: [] ``` ### Provide back office translation When the image asset field is displayed in the back office, a table of metadata follows. This example uses new fields, so you need to provide translations for their labels, for example, in `translations/ibexa_fieldtypes_preview.en.yaml`: ``` ibexa_image_asset.dam_asset.page_url: Image page ibexa_image_asset.dam_asset.author: Image author ibexa_image_asset.dam_asset.license: License ibexa_image_asset.dam_asset.license_url: License page ``` ### Add Wikimedia Commons connection to DAM configuration You can now configure a connection with Wikimedia Commons under the `ibexa.system..content.dam` key using the source identifier `commons`: ``` ibexa: system: default: content: dam: [ commons ] ``` Once you clear the cache, you can search for images to see whether images from the newly configured DAM are displayed correctly, including their variations. # Fastly Image Optimizer (Fastly IO) The Fastly Image Optimizer (Fastly IO) is an external service that provides real-time image optimization for multiple input and output formats. It serves and caches image requests from your origin server, making your website faster and more efficient. To be able to configure this feature, you need [Fastly IO subscription](https://www.fastly.com/documentation/guides/full-site-delivery/about-fastly-image-optimizer/). ## Enable shielding To use Fastly Image Optimizer, you first need a [working setup of Ibexa DXP and Fastly](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/http_cache/reverse_proxy/#using-varnish-or-fastly) with shielding enabled. To enable shielding, follow the steps in [Fastly Developer Documentation](https://www.fastly.com/documentation/guides/concepts/shielding/#enabling-and-disabling-shielding). Remember to choose a shield location from the **Shielding** menu, as described in [Fastly User Documentation](https://www.fastly.com/documentation/guides/getting-started/hosts/shielding#enabling-shielding). ## VCL configuration To manipulate your Fastly VCL configuration directly from the command line, you need to: - [install Fastly CLI](https://www.fastly.com/documentation/reference/tools/cli/#installing), - define `FASTLY_SERVICE_ID` and `FASTLY_KEY` environmental variables, - set optimizer restrictions by using the `ibexa_image_optimizer.vcl` file: ``` # Restrict optimizer by file path and extension if (req.url.ext ~ "(?i)^(gif|png|jpe?g|webp)$") { if (req.url.path ~ "^/var/([a-zA-Z0-9_-]+)/storage/images") { set req.http.x-fastly-imageopto-api = "fastly"; } } ``` You can customize what image formats are included, for example: `gif|png|jpe?g|webp`, and which paths should be used as a source of images, for example: `^/var/([a-zA-Z0-9_-]+)/storage/images`. For more configuration options, see [Enabling image optimization](https://www.fastly.com/documentation/reference/io/#enabling-image-optimization). To apply your modifications or use the default configuration as-is, you can upload the `.vcl` file from the command line: ``` fastly vcl snippet create --name="Ibexa Image Optimizer" --version=active --autoclone --type recv --content=vendor/ibexa/fastly/fastly/ibexa_image_optimizer.vcl fastly service-version activate --version=latest ``` For more information about Fastly configuration and CLI usage examples, see [Configure and customize Fastly](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/http_cache/fastly/index.md). ## Define SiteAccess for Fastly IO Fastly IO configuration is SiteAccess aware. You can define what handler should be used for a specific SiteAccess under `variation_handler_identifier` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files). You need to set it up as `fastly`, so Fastly IO can generate all image links. By default, it's set as `alias`, and it points to a built-in image optimizer. You can also set up a custom handler if your setup requires it. ``` ibexa: system: my_siteaccess: variation_handler_identifier: 'fastly' ``` You can also use environmental variables to configure a specific handler for a SiteAccess. See the example below to configure it with the `.env` file: ``` IBEXA_VARIATION_HANDLER_IDENTIFIER="fastly" ``` ## Image configuration When you define image variation keys for Fastly IO, keep in mind that they should reflect variations in your original setup. The built-in image optimizer serves as backup to Fastly IO in case of misconfiguration, so it needs to be able to serve the same image variations. Fastly IO image filters aren't compatible with Ibexa built-in filters, so you aren't able to reflect your original filters accurately with Fastly. The script below helps you find replacement filters within Fastly configuration for the basic filters. For more optimization options on Fastly side, see [Fastly IO reference](https://www.fastly.com/documentation/reference/io/). To generate your original image configuration run: ``` php bin/console ibexa:fastly:migrate-configuration ``` Paste the following configuration to define the same variations for Fastly IO: ``` ibexa: system: default: fastly_variations: reference: reference: original configuration: width: 600 height: 600 fit: bounds small: reference: reference configuration: width: 100 height: 100 fit: bounds tiny: reference: reference configuration: width: 30 height: 30 fit: bounds medium: reference: reference configuration: width: 200 height: 200 fit: bounds large: reference: reference configuration: width: 300 height: 300 fit: bounds gallery: reference: original configuration: { } ezplatform_admin_ui_profile_picture_user_menu: reference: reference configuration: width: 30 height: 30 fit: bounds crop: '30,30,x0,y0' ``` You can select defined image variations during content item creation in the image options. Variations can include different sizing options and other filters that are applied to the image. *[Image: Fastly image variations]* # RichText RichText is a type of field that you add in any content item in Ibexa DXP and edit in Online Editor. - [Online Editor product guide](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/content_management/rich_text/online_editor_guide/): Learn how to use the Online Editor, a tool that allows you to edit RichText Fields in any content item in Ibexa DXP. - [Extend Online Editor](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/content_management/rich_text/extend_online_editor/): Add custom tags, styles and data attributes to enrich the functionality of the Online Editor. Change Online Editor configuration. - [Create custom RichText block](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/content_management/rich_text/create_custom_richtext_block/): Create a custom Page block containing rich text. # Online Editor product guide ## What is Online Editor Online Editor is the interface for editing RichText fields in any content item in Ibexa DXP. It offers standard editing capabilities and extensibility points to customize the editing experience and the available elements. Online Editor is based on [CKEditor 5](https://ckeditor.com/ckeditor-5/). ## Availability Online Editor is available in all supported Ibexa DXP versions and editions. ## How to get started Online Editor is the default editing interface for all RichText fields. To start using it, create any content item with a RichText field (for example, based on the built-in Article content type) and edit this field. ## Capabilities ### Rich Text editor Online Editor covers all fundamental formatting options for rich text, such as headings, lists, tables, inline text formatting, anchors, and links. It also allows embedding other content from the repository, but also from Facebook, Twitter, or YouTube. #### Links All links added to a RichText field by using the link element are listed and can be managed in the [Link manager](https://doc.ibexa.co/en/latest/content_management/url_management/url_management/index.md). #### Distraction free mode While editing Rich Text fields, you can switch to distraction free mode that expands the workspace to full screen. *[Image: Distraction free mode]* For more information, see [Distraction free mode](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/create_edit_content_items/#distraction-free-mode). ### Custom tags Custom tags are customizable RichText elements for which you can specify attributes and render them with custom templates. Custom tags can be created by means of specifying two things only: - YAML configuration - relevant Twig templates The YAML configuration defines a custom tag’s attributes, the template used to render it, and where in the toolbar the tag is available. See [Extend Online Editor](https://doc.ibexa.co/en/latest/content_management/rich_text/extend_online_editor/#configure-custom-tags) for a full example. ### Custom styles Custom styles allow specifying custom predefined templates for specific RichText elements. Custom styles differ from custom tags in that they don't have attributes configured. A custom style requires YAML configuration that points to a template used to render an elements with this style. See [Extend Online Editor](https://doc.ibexa.co/en/latest/content_management/rich_text/extend_online_editor/#configure-custom-styles) for a full example. ### Custom data attributes and CSS classes For each RichText element type, you can configure custom data attributes or CSS classes that the user can select when working in Online Editor. Custom data attributes allow adding new attributes to existing Rich Text elements, such as headings or lists, which are added in the form of `data-ezattribute-=""`. For more information, see [Extend Online Editor](https://doc.ibexa.co/en/latest/content_management/rich_text/extend_online_editor/#custom-data-attributes). Custom CSS classes work in a similar way, giving editor a choice of classes to add to any type of element. For more information, see [Extend Online Editor](https://doc.ibexa.co/en/latest/content_management/rich_text/extend_online_editor/#custom-css-classes). ### Plugins Online Editor is based on CKEditor 5, and you can use CKEditor's capabilities to [create plugins](https://doc.ibexa.co/en/latest/content_management/rich_text/extend_online_editor/#add-ckeditor-plugins) for the editor. ## Benefits ### Familiar editing tools Online editor offers rich text editing tools familiar to most editors and contributors, which allows quick adoption to the editorial flow. *[Image: Familiar editing tools]* The editor's toolbars can be customized and reorganized to for the specific project's needs. ### Customizable text elements The range of available text elements can be extended by offering custom elements and custom formatting options. Custom formatting options can be offered either as custom CSS classes that editors can add to specific elements, or as custom styles which can have their own templates. More extensive customization is available via custom tags: - completely custom RichText elements that you can fully configure - custom CKEditor 5 plugins ## Use cases ### Customizable Call to action buttons Online Editor extensibility offers a simple way to create custom elements such as Call to action (CTA) buttons. Creating a CTA custom tag lets you use a template to construct a button element. Then, you can add a link attribute to provide target for the button, and a style attribute with different presets to style its look. *[Image: Call to action buttons]* Refer to [Extend Online Editor](https://doc.ibexa.co/en/latest/content_management/rich_text/extend_online_editor/#link-tag) for a similar use case. ### Product marketing campaigns With the Online Editor, editors can embed products from the product catalog directly into RichText fields. Products can be embedded as block-level or inline elements. You can use it to weave marketing content around your product data, showcasing your product capabilities and bringing it closer to your customers. See [Embed products in content](https://doc.ibexa.co/en/latest/product_catalog/products/#embed-products-in-content) for details. ### Embed external resources Custom tags allow embedding content from external resources inside RichText fields. The built-in elements offer embedding of Twitter or Facebook posts, but you can extend the capability by embedding other resources. These can be, for example, 3D product, or real estate viewers. # Extend Online Editor Ibexa DXP users edit the contents of RichText fields, for example, in the Content box of a Page, by using the Online Editor. You can extend the Online Editor by adding custom tags and styles, defining custom data attributes, re-arranging existing buttons, grouping buttons into custom toolbar, and creating [custom buttons](https://ckeditor.com/docs/ckeditor4/latest/guide/widget_sdk_tutorial_1.html#widget-toolbar-button) and [custom plugins](https://ckeditor.com/docs/ckeditor4/latest/guide/dev_plugins.html). Online Editor is based on the CKEditor5. Refer to [CKEditor5 documentation](https://ckeditor.com/docs/ckeditor5/latest/index.html) to learn how you can extend the Online Editor with even more elements. For more information about extending the back office, see [Extend back office](https://doc.ibexa.co/en/latest/administration/back_office/back_office/index.md). ## Configure custom tags With custom tags, you can enhance the Online Editor with features that go beyond the built-in ones. You configure custom tags under the `ibexa_richtext` key. Start preparing the tag by adding a configuration file: ``` ibexa: system: admin_group: fieldtypes: ibexa_richtext: custom_tags: [factbox] toolbar: custom_tags_group: buttons: factbox: priority: 5 ibexa_fieldtype_richtext: custom_tags: factbox: template: '@ibexadesign/field_type/ibexa_richtext/custom_tags/factbox.html.twig' icon: '/bundles/ibexaadminuiassets/vendors/ids-assets/dist/img/all-icons.svg#info-square' attributes: name: type: string required: true style: type: choice required: true default_value: light choices: [light, dark] ``` Custom tags can have as many attributes as needed. Supported attribute types are: `string`, `number`, `boolean`, `link`, and `choice`. `choice` requires that you provide a list of options in the `choices` key. You must provide your own files for the Twig template and the icon. Place the `factbox.html.twig` template in the `templates/themes//field_type/ibexa_richtext/custom_tags` directory: ```

    {{ params.name }}

    {{ content|raw }}
    ``` > **Tip: Tip** > > If an attribute isn't required, check if it's defined by adding a check in the template, for example: > > ``` > {% if params.your_attribute is defined %} > ... > {% endif %} > ``` Add labels for the new tag by providing translations in `translations/custom_tags.en.yaml`: ``` ibexa_richtext.custom_tags.factbox.label: 'Factbox' ibexa_richtext.custom_tags.factbox.attributes.name.label: 'Name' ibexa_richtext.custom_tags.factbox.attributes.style.label: 'Style' ``` Now you can use the tag. In the back office, create or edit a content item that has a RichText field type. In the Online Editor, click **Add**, and from the list of available tags select the FactBox tag icon. *[Image: FactBox Tag]* ### Inline custom tags You can also place custom tags inline with the following configuration: ``` ibexa_fieldtype_richtext: custom_tags: acronym: template: '@ibexadesign/field_type/ibexa_richtext/custom_tags/acronym.html.twig' icon: '/bundles/ibexaadminuiassets/vendors/ids-assets/dist/img/all-icons.svg#edit' is_inline: true attributes: # ... ``` `is_inline` is an optional key. The default value is `false`, therefore, if it's not set, the custom tag is treated as a block tag. ### Use cases #### Link tag You can configure a custom tag with a `link` attribute that offers a basic UI with text input. It's useful when migrating from eZ Publish to Ibexa DXP. The configuration is: ``` ibexa: system: admin_group: fieldtypes: ibexa_richtext: custom_tags: [linktag] toolbar: custom_tags_group: buttons: linktag: priority: 6 ibexa_fieldtype_richtext: custom_tags: linktag: template: '@ibexadesign/field_type/ibexa_richtext/custom_tags/linktag.html.twig' icon: '/bundles/ibexaadminuiassets/vendors/ids-assets/dist/img/all-icons.svg#link' is_inline: true attributes: title: type: string required: false description: type: string required: false color: type: choice required: false choices: [Red, Blue, Green] url: type: link required: false ``` Provide your own files for the Twig template and the icon. The tag has the `url` attribute with the `type` parameter set as `link` (lines 30-31). Then create the `templates/themes//field_type/ibexa_richtext/custom_tags/linktag.html.twig` template: ```

    Custom link

    {% for attr_name, attr_value in params %}
    {{ attr_name }}: {{ attr_value }}
    {% endfor %} ``` Add labels for the tag by providing translations in `translations/custom_tags.en.yaml`: ``` ibexa_richtext.custom_tags.linktag.label: 'Link Tag' ibexa_richtext.custom_tags.linktag.attributes.title.label: 'Title' ibexa_richtext.custom_tags.linktag.attributes.description.label: 'Description' ibexa_richtext.custom_tags.linktag.attributes.color.label: 'Color' ibexa_richtext.custom_tags.linktag.attributes.url.label: 'URL' ``` Now you can use the tag. In the back office, create or edit a content item that has a RichText field type. In the Online Editor's toolbar, click **Show more items**, and from the list of available tags select the Link tag icon. *[Image: Link Tag]* #### Acronym You can create an inline custom tag that displays a hovering tooltip with an explanation of an acronym. ``` ibexa: system: admin_group: fieldtypes: ibexa_richtext: custom_tags: [acronym] toolbar: custom_tags_group: buttons: acronym: priority: 7 ibexa_fieldtype_richtext: custom_tags: acronym: template: '@ibexadesign/field_type/ibexa_richtext/custom_tags/acronym.html.twig' icon: '/bundles/ibexaadminuiassets/vendors/ids-assets/dist/img/all-icons.svg#edit' is_inline: true attributes: explanation: type: string ``` The `explanation` attribute contains the meaning of the acronym that is provided while editing in the Online Editor. Add labels for the tag by providing translations in `translations/custom_tags.en.yaml`: ``` ibexa_richtext.custom_tags.acronym.label: 'Acronym' ibexa_richtext.custom_tags.acronym.attributes.explanation.label: 'Explanation' ``` *[Image: Adding an explanation to an Acronym custom tag]* In the template file `acronym.html.twig` provide the explanation as attribute value to the title of the `abbr` tag: ``` {{ content }} ``` *[Image: Acronym custom tag]* ## Configure custom styles You can extend the Online Editor with custom text styles. The styles are available in the text toolbar when a section of text is selected. There are two kinds of custom styles: block and inline. Inline styles apply to the selected portion of text only, while block styles apply to the whole paragraph. Start creating a custom style by providing configuration: - a global list of custom styles, defined under the node `ibexa_richtext.custom_styles`, - a list of enabled custom styles for a given `admin` SiteAccess or `admin_group` SiteAccess group, located under the node `ibexa.system..fieldtypes.ibexa_richtext.custom_styles` A sample configuration could look as follows: ``` ibexa: system: admin_group: fieldtypes: ibexa_richtext: custom_styles: [highlighted_block, highlighted_word] ibexa_fieldtype_richtext: custom_styles: highlighted_word: template: '@ibexadesign/field_type/ibexa_richtext/custom_styles/highlighted_word.html.twig' inline: true highlighted_block: template: '@ibexadesign/field_type/ibexa_richtext/custom_styles/highlighted_block.html.twig' inline: false ``` > **Note: Note** > > Currently, if you define these lists for a front site SiteAccess, it has no effect. Add labels for the new styles by providing translations in `translations/custom_styles.en.yaml`: ``` ibexa_richtext.custom_styles.highlighted_block.label: Highlighted block ibexa_richtext.custom_styles.highlighted_word.label: Highlighted word ``` ### Rendering The `template` key points to the template that is used to render the custom style. It's recommended that you use the [design engine](https://doc.ibexa.co/en/latest/templating/design_engine/design_engine/index.md). The template files for the front end could look as follows: - `templates/themes/standard/field_type/ibexa_richtext/custom_styles/highlighted_word.html.twig`: ``` {% apply spaceless %}{{ content|raw }}{% endapply %} ``` - `templates/themes/standard/field_type/ibexa_richtext/custom_styles/highlighted_block.html.twig`: ```
    {% apply spaceless %}{{ content|raw }}{% endapply %}
    ``` Templates for Content View in the back office would be `templates/themes/admin/field_type/ibexa_richtext/custom_styles/highlighted_word.html.twig` and `templates/themes/admin/field_type/ibexa_richtext/custom_styles/highlighted_block.html.twig` (assuming that the back office SiteAccess uses the default `admin` theme). ### Use cases #### Note box You can create a custom style that places a paragraph in a note box: *[Image: Example of a note box custom style]* ``` ibexa: system: admin_group: fieldtypes: ibexa_richtext: custom_styles: [note_box] ibexa_fieldtype_richtext: custom_styles: note_box: template: field_type/ibexa_richtext/custom_styles/note_box.html.twig ``` The `note_box.html.twig` template wraps the content of the selected text (`{{ content }}`) in a custom CSS class: ```
    {{ content }}
    ``` You can now define the custom CSS for this template, for example by using [Webpack Encore and assets](https://doc.ibexa.co/en/latest/templating/assets/index.md): ``` .note { display: block; background-color: #faa015; border-left: solid 5px #353535; line-height: 18px; padding: 15px; color: #fff; font-weight: bold; } ``` Add label for the new style by providing a translation in `translations/custom_styles.en.yaml`: ``` ibexa_richtext.custom_styles.note_box.label: 'Note box' ``` *[Image: Adding a Note box custom style]* > **Tip: Tip** > > You can also create a similar note box with [custom classes](#note-box_1). #### Text highlight You can create an inline custom style that highlights a part of a text: *[Image: Example of a custom style highlighting a portion of text]* ``` ibexa: system: admin_group: fieldtypes: ibexa_richtext: custom_styles: [highlight] ibexa_fieldtype_richtext: custom_styles: highlight: template: field_type/ibexa_richtext/custom_styles/highlight.html.twig inline: true ``` The `highlight.html.twig` template wraps the content of the selected text (`{{ content }}`) in a custom CSS class: ``` {{ content }} ``` You can now define the custom CSS for this template, for example by using [Webpack Encore and assets](https://doc.ibexa.co/en/latest/templating/assets/index.md): ``` .highlight { background-color: #fcc672; border-radius: 25% 40% 25% 40%; } ``` Add label for the new style by providing a translation in `translations/custom_styles.en.yaml`: ``` ibexa_richtext.custom_styles.highlight.label: 'Highlight' ``` *[Image: Adding a Highlight custom style]* ## Configure custom data attributes and classes You can add custom data attributes and CSS classes to the following elements in the Online Editor: - `embedInline` - `embed` - `formatted` - `heading` - `heading1` to `heading6` - `embedImage` - `ul` - `ol` - `li` - `paragraph` - `table` - `tr` - `td` - `link` > **Note: Heading elements** > > `heading` applies to all heading elements, and `heading1` to `heading6` to specific heading levels. > > When you configure both `heading` and a specific heading level (for example, `heading2`) at the same time, only the more specific configuration applies, in this case, `heading2`. > **Caution: Overriding embed templates** > > If you override the default templates for `embedInline`, `embed` or `embedImage` elements, for example, `@IbexaCore/default/content/embed.html.twig`, the data attributes and classes aren't rendered automatically. > > Instead, you can make use of the `data_attributes` and `class` properties in your templates. With the `ibexa_data_attributes_serialize` helper you can serialize the data attribute array. ### Custom data attributes You configure custom data attributes under the `fieldtypes.ibexa_fieldtype_richtext.attributes` key. The configuration is SiteAccess-aware. A custom data attribute can belong to one of the following types: `choice`, `boolean`, `string`, or `number`. You can also set each attribute to be `required` and set its `default_value`. For the `choice` type, you must provide an array of available `choices`. By adding `multiple`, you can decide whether more than one option can be selected. It's set to `false` by default. Use the example below to add two data attributes, `custom_attribute` and `another_attribute` to the Heading element in the `admin_group` SiteAccess: ``` ibexa: system: admin_group: fieldtypes: ibexa_richtext: attributes: heading: custom-attribute: type: boolean default_value: false another-attribute: type: choice choices: [attr1, attr2] default_value: attr2 required: false multiple: true ``` The configuration outputs `data-ezattribute-=""` in the corresponding HTML element. Here, the resulting values are `data-ezattribute-custom-attribute="false"` and `data-ezattribute-another-attribute="attr1,attr2"`. ### Custom CSS classes You configure custom CSS classes under the `fieldtypes.ibexa_richtext.classes` key. The configuration is SiteAccess-aware. You must provide the available `choices`. You can also set the values for `required`, `default_value` and `multiple`. `multiple` is set to true by default. Use the example below to add a class choice to the Paragraph element in the `admin_group` SiteAccess: ``` ibexa: system: admin_group: fieldtypes: ibexa_richtext: classes: paragraph: choices: [regular, special, tip_box, warning_box] default_value: regular required: false multiple: false ``` > **Note: Label translations** > > If there are many custom attributes, to provide label translations for these attributes, you can use the `ez_online_editor_attributes` translation extractor to get a full list of all custom attributes for all elements in all scopes. > > For example: > > ``` > php ./bin/console jms:translation:extract --enable-extractor=ez_online_editor_attributes \ > --dir=./templates --output-dir=./translations/ --output-format=yaml > ``` ### Use cases #### Note box You can create a custom class that enables you to place a paragraph element in a note box: *[Image: Example of a note box custom style]* ``` ibexa: system: admin_group: fieldtypes: ibexa_richtext: classes: paragraph: choices: [regular, special, tip_box, warning_box] ``` With this class you can choose one of the following classes for each paragraph element: `regular`, `tip_box`, or `warning_box`. You can then style the class by using CSS. *[Image: Selecting a custom style for a paragraph]* > **Tip: Tip** > > You can also create a similar note box with [custom styles](#note-box). ## Rearrange buttons You can modify the order and visibility of buttons that are available in the Online Editor toolbar through configuration: ``` ibexa: system: admin_group: fieldtypes: ibexa_richtext: custom_tags: [ezyoutube, eztwitter, ezfacebook] toolbar: group1: priority: 60 buttons: ibexaMoveUp: priority: 30 ibexaMoveDown: priority: 20 heading: priority: 10 group2: priority: 50 buttons: alignment: priority: 10 ``` For each button you can set `priority`, which defines the order of buttons in the toolbar. For a full list of standard buttons, see the RichText module's [configuration file](https://github.com/ibexa/fieldtype-richtext/blob/main/src/bundle/Resources/config/prepend/ezpublish.yaml) ## Add CKEditor plugins Regular CKEditor plugins can be added to the Online Editor. This procedure is illustrated with the addition of the [Special characters plugin](https://ckeditor.com/docs/ckeditor5/latest/features/special-characters.html). You can install a CKEditor plugin locally by using `yarn add` or `npm install`, and deploy it by committing the `yarn.lock` file. A local installation looks like: ``` yarn add @ckeditor/ckeditor5-special-characters@40.2.0 ``` Make sure to specify a version range compatible with the CKEditor's version used in Ibexa DXP. The CKEditor plugin must be added to the `ibexa.richText.CKEditor.extraPlugins` array. For this purpose, create an `assets/js/richtext.ckeditor-plugins.js` to import the plugin elements and add them to the array using `ibexa.addConfig` : ``` // The plugin itself import SpecialCharacters from '../../node_modules/@ckeditor/ckeditor5-special-characters/src/specialcharacters'; // The character list that will be used by the plugin import SpecialCharactersEssentials from '../../node_modules/@ckeditor/ckeditor5-special-characters/src/specialcharactersessentials'; ibexa.addConfig('richText.CKEditor.extraPlugins', [ SpecialCharacters, SpecialCharactersEssentials ], true); ``` The plugin is imported from `../../node_modules/@ckeditor` path and not directly from `@ckeditor` alias because this alias points at `./public/bundles/ibexaadminuiassets/vendors/@ckeditor`. Add the previous file to `ibexa-richtext-onlineeditor-js` Webpack Encore entry. Create the following `encore/ibexa.richtext.config.manager.js` file: ``` const path = require('path'); module.exports = (ibexaConfig, ibexaConfigManager) => { ibexaConfigManager.add({ ibexaConfig, entryName: 'ibexa-richtext-onlineeditor-js', newItems: [path.resolve(__dirname, '../assets/js/richtext.ckeditor-plugins.js')], }); }; ``` See [Importing assets from a bundle](https://doc.ibexa.co/en/latest/administration/back_office/back_office_elements/importing_assets_from_bundle/index.md) for alternative ways to add files to Webpack Encore entries. Add the plugin button to the RichText toolbar config (under `ibexa.system..fieldtypes.ibexa_richtext.toolbar`). A new button group is defined in `config/packages/ibexa_admin_ui.yaml` with [the `specialcharacters` button exposed by the plugin API](https://ckeditor.com/docs/ckeditor5/latest/features/special-characters.html#common-api): ``` ibexa: # … system: admin_group: # … fieldtypes: ibexa_richtext: toolbar: my_group: priority: 25 buttons: specialCharacters: priority: 10 ``` Build the assets and clear the cache by running `composer run-script auto-scripts`. For more information, see [CKEditor plugins documentation](https://ckeditor.com/docs/ckeditor5/latest/framework/architecture/plugins.html). ## Change CKEditor configuration You can add or override CKEditor configuration to set one of the [available properties](https://ckeditor.com/docs/ckeditor5/latest/api/module_core_editor_editorconfig-EditorConfig.html). To do it, add a custom config object to the `window.ibexa.richText.CKEditor.extraConfig` key by using the `addConfig` method: ``` window.ibexa.addConfig('richText.CKEditor.extraConfig', {your_custom_config_object}, true); ``` To have `Arrows` category from [previously added Special characters plugin](#add-ckeditor-plugins) on [top of the filter menu](https://ckeditor.com/docs/ckeditor5/latest/features/special-characters.html#ordering-categories): ``` ibexa.addConfig('richText.CKEditor.extraConfig', { specialCharacters: { order: ['Arrows'] } }, true); ``` *[Image: CKEditor Special characters: Arrows category on top of the character filter]* You can also use custom functions to modify the plugin configuration. The following example adds two ways to add a non-breaking space character: ``` function SpecialCharactersNbsp( editor ) { // add non-breaking space to the SpecialCharacters plugin editor.plugins.get( 'SpecialCharacters' ).addItems( 'Text', [ { title: 'Non-Breaking Space', character: '\u00a0' } ] ); // add a keyboard shortcut editor.keystrokes.set( 'Ctrl+space', ( key, stop ) => { editor.execute( 'input', { text: '\u00a0' } ); stop(); } ); } ibexa.addConfig('richText.CKEditor.extraPlugins', [ SpecialCharacters, SpecialCharactersEssentials, SpecialCharactersNbsp ], true); ``` # Create custom RichText block Editions: Experience A RichText block is a specific example of a [custom block](https://doc.ibexa.co/en/latest/content_management/pages/create_custom_page_block/index.md) that you can use when you create a page. To create a custom block, you must define the block's layout, provide templates, add a subscriber, and register the subscriber as a service. Follow the procedure below to create a RichText page block. First, provide the block configuration under the `ibexa_page_fieldtype.blocks` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files). The following code defines a new block, its view and configuration templates. It also sets the attribute type to `richtext` (line 15): ``` ibexa_fieldtype_page: blocks: my_block: name: My Richtext Block thumbnail: assets/images/blocks/richtext_block_icon.svg configuration_template: '@ibexadesign/blocks/my_block/config.html.twig' views: default: template: '@ibexadesign/blocks/my_block/default.html.twig' name: My block view priority: -255 attributes: content: name: Content type: richtext ``` > **Note: Note** > > Make sure that you provide an icon for the block in the `assets/images/blocks/` folder. Then, create a subscriber that converts a string of data into XML code. Create a `src/Event/Subscriber/RichTextBlockSubscriber.php` file. In line 32, `my_block` is the same name of the block that you defined in line 3 above. Line 32 also implements the `PreRender` method. Lines 41-51 handle the conversion of content into an XML string: ``` 'onBlockPreRender', ]; } /** * @param \Ibexa\FieldTypePage\FieldType\Page\Block\Renderer\Event\PreRenderEvent $event */ public function onBlockPreRender(PreRenderEvent $event): void { $renderRequest = $event->getRenderRequest(); if (!$renderRequest instanceof TwigRenderRequest) { return; } $parameters = $renderRequest->getParameters(); $parameters['document'] = null; $xml = $event->getBlockValue()->getAttribute('content')->getValue(); if (!empty($xml)) { $parameters['document'] = $this->domDocumentFactory->loadXMLString($xml); } $renderRequest->setParameters($parameters); } } ``` Now you can create [templates](https://doc.ibexa.co/en/latest/templating/templates/templates/index.md) that are used for displaying and configuring your block. Create the view template in `templates/themes//blocks/my_block/richtext.html.twig`. Line 2 is responsible for rendering the content from XML to HTML5: ```
    {{ document | ibexa_richtext_to_html5 }}
    ``` Then, create a separate `templates/themes/admin/blocks/my_block/config.html.twig` template: ``` {% extends '@IbexaPageBuilder/page_builder/block/config.html.twig' %} {% block meta %} {{ parent() }} {% endblock %} ``` Finally, register the subscriber as a service in `config/services.yaml`: ``` services: App\Event\Subscriber\RichTextBlockSubscriber: tags: - { name: kernel.event_subscriber } ``` You have successfully created a custom RichText block. You can now add your block in the **Site** tab. *[Image: RichText block]* For more information about customizing additional options of the block or creating custom blocks with other attribute types, see [Create custom Page block](https://doc.ibexa.co/en/latest/content_management/pages/create_custom_page_block/index.md). # File management ## Access binary files To access binary files from the PHP API, use the `Ibexa\Core\IO\IOServiceInterface::loadBinaryFile()` method: ``` $file = $this->ioService->loadBinaryFile($field->value->id); $fileContent = $this->ioService->getFileContents($file); ``` ## Handling binary files Ibexa DXP supports multiple binary file handling mechanisms by means of an `IOHandler` interface. This feature is used by the [BinaryFile](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/imagefield/index.md) field types. ### Native IO handler The IO API is organized around two types of handlers, both used by the IOService: - `Ibexa\Core\IO\IOMetadataHandler`: stores and reads metadata (such as validity or size) - `Ibexa\Core\IO\IOBinarydataHandler`: stores and reads the actual binary data You can configure IO handlers using semantic configuration. IO handlers are configurable per SiteAccess. See the default configuration: ``` ibexa: system: default: io: metadata_handler: dfs binarydata_handler: nfs ``` The adapter is the *driver* used by Flysystem v2 to read/write files. Adapters are declared using `oneup_flysystem`. Metadata and binary data handlers are configured under `ibexa_io`. See below the configuration for the default handlers. It declares a metadata handler and a binary data handler, both labeled `default`. Both handlers are of type `flysystem`, and use the same Flysystem v2 adapter, labeled `default` as well. ``` ibexa_io: binarydata_handlers: nfs: flysystem: adapter: nfs_adapter metadata_handlers: dfs: legacy_dfs_cluster: connection: doctrine.dbal.dfs_connection ``` The `nfs_adapter`'s directory is based on your site settings, and is automatically set to `$var_dir$/$storage_dir$` (for example, `/path/to/ibexa/public/var/site/storage`). #### Permissions of generated files You can configure permissions of generated files under the `ibexa.system..io.permissions` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files). ``` ibexa: system: default: io: permissions: files: 0750 #default is 0644 directories: 0640 #default is 0755 ``` Both `files` and `directories` are optional. Default values: - 0644 for files - 0755 for directories > **Note: Note** > > Make sure to configure permissions using a number and **not** a string. "0644" is **not** interpreted by PHP as an octal number, and unexpected permissions can be applied. > **Note: Note** > > As SiteAccess configuration Flysystem's v2 native Local NFS adapter isn't supported, the following configuration should be used: > > ``` > oneup_flysystem: > adapters: > nfs_adapter: > custom: > service: ibexa.io.nfs.adapter.site_access_aware > ``` ### Native Flysystem v2 handler Ibexa DXP uses it as the default way to read and write content in form of binary files. Flysystem v2 can use the `local` filesystem, but is also able to read/write to `sftp`, `zip` or cloud filesystems (`azure`, `rackspace`, `S3`). [league/flysystem](https://flysystem.thephpleague.com/docs/) (along with [FlysystemBundle](https://github.com/1up-lab/OneupFlysystemBundle/)) is an abstract file handling library. #### Handler options ##### Adapter To be able to rely on dynamic SiteAccess-aware paths, you need to use Ibexa custom `nfs_adapter`. A basic configuration might look like the following: ``` oneup_flysystem: adapters: nfs_adapter: custom: service: ibexa.io.nfs.adapter.site_access_aware ``` To learn how to configure other adapters, see the [bundle's online documentation](https://github.com/1up-lab/OneupFlysystemBundle/blob/main/doc/index.md#step3-configure-your-filesystems). > **Note: Note** > > Only the adapters are used here, not the filesystem configuration described in this documentation. ### DFS Cluster handler For clustering, the platform provides a custom metadata handler that stores metadata about your assets in the database. This is faster than accessing the remote NFS or S3 instance to read metadata. For more information, see [Clustering](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/clustering/clustering/index.md). # Binary and Media download You can restrict files stored in BinaryFile or Media fields to certain user roles. These files aren't publicly downloadable from disk, and are instead served by Symfony, using a custom route that runs the necessary checks. This route is automatically generated as the `url` property for those field values. ## `content/download` route You have to create a route using the `download` route name. It accepts optional query parameters: - `version`: The content version number that the file is downloaded for. Requires the `content / read` permission for a published version and additionally `content / versionread` permission for an unpublished version. If not specified, uses the published version. - `inLanguage`: The language the file should be downloaded in. If not specified, the most prioritized language for the SiteAccess is used. The [`ibexa_render_field`](https://doc.ibexa.co/en/latest/templating/twig_function_reference/field_twig_functions/#ibexa_render_field) Twig helper by default generates a working link. ## Download link generation To generate a direct download link for a File field you have to create a [RouteReference](https://doc.ibexa.co/en/latest/templating/urls_and_routes/urls_and_routes/#routereference) with the `ibexa_route` helper, passing `content` and File field identifier as parameters. Optional parameter `inLanguage` may be used to specify File content translation. ``` {% set routeReference = ibexa_route( 'ibexa.content.download', {'content': content, 'fieldIdentifier': 'file', 'inLanguage': content.prioritizedFieldLanguageCode } ) %} Download ``` ## REST API: `uri` property The `uri` property of Binary fields in REST contains a valid download URL, of the same format as the public PHP API, prefixed with the same host as the REST Request. For [more information about REST API see the documentation](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_usage/rest_api_usage/index.md). # File URL handling ### IO URL decoration By default, images and binary files that are referenced by the content are served from the same server as the application, for example `/var/site/storage/images/3/6/4/6/6463-1-eng-GB/kidding.png`. This is the default semantic configuration: ``` ibexa: system: default: io: url_prefix: '$var_dir$/$storage_dir$' ``` `$var_dir$` and `$storage_dir$` are dynamic, [SiteAccess-aware settings](https://doc.ibexa.co/en/latest/multisite/siteaccess/siteaccess_aware_configuration/index.md), and are replaced by their values in the execution context. ## Serving images with nginx One common use case is to use an optimized nginx to serve images in an optimized way. The previous example image could be made available as `http://static.example.com/var/site/storage/images/3/6/4/6/6463-1-eng-GB/kidding.png` by setting up a separate server that maps the `/path/to/ibexa/public/var` directory. The configuration would be as follows: ``` ibexa: system: default: io: url_prefix: 'https://static.example.com/$var_dir$/$storage_dir$' ``` > **Caution: Caution** > > For security reasons, don't map `/path/to/ibexa/public/` as Document Root of the static server. Map the `/var/` directory directly to `/path/to/ibexa/public/var` instead. ## `io.url_prefix` Any BinaryFile returned by the public PHP API is prefixed with the value of this setting, internally stored as `ibexa.site_access.config..io.url_prefix`. ### `io.url_prefix` dynamic service container setting Default value: `$var_dir$/$storage_dir$` Example: `/var/site/storage` You can use `io.url_prefix` to configure the default URL decorator service (`ibexa.core.io.default_url_decorator`), used by all binary data handlers to generate the URI of loaded files. It's always interpreted as an absolute URI, meaning that unless it contains a scheme (`http://`, `ftp://`), is prepended with a `/`. This setting is SiteAccess-aware. ### Services #### URL decorators A `Ibexa\Core\IO\UrlDecorator` decorates and undecorates a specified string (URL). It has two mirror methods: `decorate` and `undecorate`. Two implementations are provided: `Prefix`, and `AbsolutePrefix`. They both add a prefix to a URL, but `AbsolutePrefix` ensures that unless the prefix is an external URL, the result is prepended with `/`. Three URL decorator services are introduced: - `Ibexa\Core\IO\UrlDecorator\AbsolutePrefix` used by the binary data handlers to decorate all URIs sent out by the API. Uses `AbsolutePrefix`. - `Ibexa\Core\IO\UrlDecorator\Prefix` used through the `UrlRedecorator` by various legacy elements (for example, converter or storage gateway) to generate its internal storage format for URIs. Uses a `Prefix`, not an `AbsolutePrefix`, meaning that no leading `/` is added. In addition, a URL redecorator service, `Ibexa\Core\IO\UrlDecorator\Prefix`, uses both previously mentioned decorators to convert URIs between what is used on the new stack, and what format legacy expects (relative URLs from the project root). # Pages Pages are block-based special types of content that editors can create and modify by using a visual drag-and-drop editor. - [Page Builder product guide](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/content_management/pages/page_builder_guide/): Read about the Page Builder - a powerful tool for creating and modifying pages in Ibexa DXP. - [Page blocks](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/content_management/pages/page_blocks/): Use blocks to customize the content of a Page with dynamic content. - [Page block attributes](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/content_management/pages/page_block_attributes/): Page blocks can contain multiple attributes, of both built-in and custom types. - [Page block validators](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/content_management/pages/page_block_validators/): Set up rules for validating Page block content. - [Create custom Page block](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/content_management/pages/create_custom_page_block/): Create and configure custom Page blocks to add customized content to Pages. # Page Builder product guide Editions: Experience ## What is page [Page](https://doc.ibexa.co/en/latest/content_management/pages/pages/index.md) is a block-based type of content. You can create and modify it with a visual drag-and-drop editor - Page Builder. Page is divided into zones into which you can drop various dynamic blocks. By editing pages you can customize the layout and content of your website. ### Create page To create a new page: 1. In the main menu, go to **Content**. 1. Select **Content structure**. 1. On the right-side toolbar, click **Create content**. 1. From the list of content items select **Landing Page**. 1. Select the layout and click **Create**. *[Image: Create page]* ### Edit page You can edit any existing page with the Page Builder. To do it, in the back office go to **Content** and select **Content structure**. Then, from the content tree choose the page and click **Edit**. ## What is Page Builder Page Builder is a visual tool that allows you to create and edit any page in Ibexa DXP. It's more than managing: it's about building pages, creating customized content and fully-targeted landing pages. Creating pages in Page Builder involves composing content from ready-to-use elements - blocks, properly configured and customized. It's also important to choose a layout - it determines the arrangement of drop zones that contain content elements. *[Image: Page Builder - diagram]* ### Availability Page Builder is available in Ibexa Experience and Ibexa Commerce. ### How does Page Builder work #### Page Builder interface Page Builder has plain and intuitive interface. You can create a Page without having advanced technical skills. *[Image: Page Builder interface]* Page Builder user interface consists of: A. Drop zone B. Page blocks / Structure view toolbar C. Settings toolbar (including Fields, Visibility and Schedule settings) D. Mode toolbar (including PC, tablet and mobile mode) E. Buttons: | Button | Description | | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | | *[Image: Edit and preview switch]* | Access main properties of the page, like title and description. | | *[Image: Preview segments]* | Access preview of the page for a given segment. | | *[Image: Timeline button]* | Access the timeline to preview how the page changes with time. You can also view the list of all upcoming scheduled events. | | *[Image: View toggler]* | Toggle through to see how the page is rendered on different devices. | | *[Image: Page blocks menu]* | Move Page blocks / Structure view to the other side of the screen. | | *[Image: Undo]* | Undo latest change. | | *[Image: Redo]* | Redo latest change. | F. Saving options | Option | Description | | ----------------------- | -------------------------------------------------- | | Close | Close the page without saving it. | | Send to review | Save the page and send it to review. | | Publish / Publish later | Publish the page or schedule publishing for later. | | Save draft | Save the page draft\*. | | Delete draft | Delete the page draft. | \*To help you preserve your work, system saves drafts of content items automatically. For more information, see [Autosave](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/content_versions/#autosave). Page Builder has two main views that you can use while creating a page: - Page blocks toolbar - consists of all available elements that you can use by dragging them and dropping on a drop zone. *[Image: Page blocks]* - Structure view - shows a structure of the page, including its division into zones and the blocks that it contains. It follows the behavior of the content tree. Structure view has ability to reorder blocks using drag and drop. *[Image: Structure view]* ##### Choose layout For newly created Page you can choose a [layout](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/configure_ct_field_settings/#available-page-layouts) which defines the available zones. Applying a layout divides the Page into the defined zones. The zones are placeholders for content items. On the Page creation modal, select the layout and click **Create draft**. Now you're ready to add blocks of content to the Page. The page layouts that an editor has access to are up to you to choose. In the `Select layouts` section, you can select layouts that you want to be available for the Page. *[Image: Switch layout]* The default, built-in Page layout has only one zone, but developers can create other layouts in configuration. For more information, see [Configure layout](https://doc.ibexa.co/en/latest/templating/render_content/render_page/#configure-layout). #### Add blocks To customize your page in Page Builder you need to add blocks. To do it, access Page blocks toolbar, drag page block that you want to use, and drop it on the empty place on a drop zone. When you add a new block to the drop zone, drop it in the blue highlighted area. Before you drop it, a bold line appears - it helps you see the position of the newly added block in relation to other, already added blocks. *[Image: Drop zone line]* Ready-to-use blocks available in Ibexa DXP have their own, unique functions, but you can also [add your own, custom blocks](https://doc.ibexa.co/en/latest/content_management/pages/create_custom_page_block/index.md). All available tools and settings, that Page Builder comes with, enable you to customize the content appearing on the page. You can check all ready-to-use blocks available in Page Builder in User Documentation, [Block reference page](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/block_reference/). #### Work with blocks Working with blocks is intuitive. You don't have to worry about placing blocks in the proper place from the start - you can reorder them at any time. You can reorder blocks in a few ways: - drag and drop block in the desired location on a drop zone - select block and use up and down arrow on the keyboard - access Structure view and use 'Move up' and 'Move down' function in the settings of the block or drag and drop to change the position in the structure *[Image: Structure view - drag and drop]* You can manage each block by accessing its settings. To do it, click settings icon next to the block's name. *[Image: Block settings]* Available settings are: - Move up - allows you to change position of the block on the page by moving it up - Move down - allows you to change position of the block on the page by moving it down - Configuration - allows you to access configuration window - Duplicate - duplicates a block with its settings, by creating a copy of it that appears below the original block - Refresh - refreshes preview of the block - Delete - deletes existing block #### Distraction free mode While configuring blocks that include Rich Text section, for example, Text block, you can switch to distraction free mode that expands the workspace to full screen. *[Image: Distraction free mode]* For more information, see [Distraction free mode](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/create_edit_content_items/#distraction-free-mode). #### Schedule content Page Builder comes with a Scheduler, it allows you to schedule content appearance. You can schedule content to be revealed, or hidden in Page Builder in two ways with: - **Scheduler tab** - it's available in the configuration of all Page blocks. In this tab you can set the date and time when the block becomes visible and when it disappears from a Page. *[Image: Scheduler tab]* - **Content Scheduler** - it's one of the blocks available in Page Builder Page blocks menu. To proceed with the schedule, go to **Basic** tab of the block, then click **Select content** and confirm your choice. Then set date and time in the **Content airtime settings** window. *[Image: Content Scheduler]* For more information, see [Schedule publication](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/schedule_publishing/). ## Benefits ### Manage your pages without technical skills Thanks to intuitive and plain Page Builder interface, you can create and manage your website without the need of having advanced technical skills. Page blocks toolbar, visible page zones and Structure view - these are the elements that make working with Page Builder really intuitive and quick. ### Self schedule content, special offers and campaigns One of the most important tools that Page Builder offers, is a Scheduler. It allows you to set and schedule a specific date and time for the content to be published or hidden. As a result, you can manage timeline of publications, without the need of manual publishing, or hiding each of them. ### Create high-converting and fully-targeted landing pages Page Builder allows you to create highly customizable websites. You can build modifiable and targeted landing pages that meet your needs. Each dynamic blocks has its own settings, properties and design that you can set up in your way to customize the content appearing on the page. Additionally, if you feel comfortable with your technical skills, you can configure your own elements, for example, a new customized layout, or block. ### Increase sales with highly personalized campaigns Personalized campaigns are one of the factors that can increase your sales. With Page Builder you can achieve it, by using customization and time Scheduler. Anytime you can edit your page and change a position of a block to enhance visibility. Additionally, Page Builder offers you a selection of ready-to-use page blocks that can help you to create content tailored to each individual customer: A. **Default** blocks: - Dynamic targeting - embeds recommended items based on the segment the user belongs to. - Personalized - displays a list of content items/products that are recommended to end users when specific scenarios are triggered. - Targeting - embeds a content item based on the segment the user belongs to. B. **PIM** blocks: - Last purchased - displays a list of products that were recently purchased from PIM. - Last viewed - displays a list of products from PIM that were recently viewed. - Product collection - displays a list of specifically selected products. - Recently added - displays a list of products that were recently added to PIM. C. **Commerce** blocks: - Bestsellers - displays a list of products from PIM that were recently a bestseller. - Orders - displays a list of orders associated with a particular company or individual customer. # Page blocks Page blocks are configured in YAML files, under the `ibexa_fieldtype_page` key. Keep in mind that Page block configuration isn't SiteAccess-aware. Ibexa DXP ships with a number of page blocks. For a list of all page blocks that are available out-of-the-box, see [Page block reference](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/block_reference/). For information on how to create and configure new layouts for the Page, see [Page layouts](https://doc.ibexa.co/en/latest/templating/render_content/render_page/#render-a-layout). > **Caution: Clear the persistence cache** > > Persistence cache must be cleared after any modifications have been made to the block config in Page Builder, such as adding, removing or altering the page blocks, block attributes, validators or views configuration. > > To clear the persistence cache run `./bin/console cache:pool:clear [cache-pool]` command. The default cache-pool is named `cache.tagaware.filesystem`. The default cache-pool when running Redis or Valkey is named `cache.redis`. If you have customized the [persistence cache configuration](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/persistence_cache/#what-is-cached), the name of your cache pool might be different. > > In prod mode, you also need to clear the symfony cache by running `./bin/console c:c`. In dev mode, the Symfony cache is rebuilt automatically. ## Block configuration Each configured block has an identifier and the following settings: | Setting | Description | | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `name` | Name of the block used in the Page Builder interface. Translatable using the `ibexa_page_fieldtype` translation domain. | | `category` | Category in the Page Builder **Page blocks** toolbox that the block is shown in. Translatable using the `ibexa_page_fieldtype` translation domain. | | `thumbnail` | Thumbnail used in the Page Builder **Page blocks** toolbox. | | `views` | Available [templates for the block](#block-templates). | | `visible` | (Optional) Toggles the block's visibility in the Page Builder **Page blocks** toolbox. Remove the block from the layout before you publish another version of the page. | | `configuration_template` | (Optional) Template for the block settings modal. | | `attributes` | (Optional) List of [block attributes](https://doc.ibexa.co/en/latest/content_management/pages/page_block_attributes/index.md). | | `cacheable_query_params` | (Optional) List of query parameters the block's [ESI HTTP cache](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/http_cache/http_cache_configuration/#when-to-use-esi) varies on. For example, if the block is paginated using `?page=ℕ` from the page URL, add `page` to this list. See [`ibexa_append_cacheable_query_params()`Twig function](https://doc.ibexa.co/en/latest/templating/twig_function_reference/page_twig_functions/#ibexa_append_cacheable_query_params). | For example: ``` ibexa_fieldtype_page: blocks: event: name: event_block.name category: custom_category.name thumbnail: /bundles/ibexaadminuiassets/vendors/ids-assets/dist/img/all-icons.svg#calendar configuration_template: '@ibexadesign/blocks/event/config.html.twig' views: default: template: '@ibexadesign/blocks/event/template.html.twig' name: event_block.view.default priority: -255 attributes: # ... ``` > **Tip: Tip** > > For a full example of block configuration, see [Create custom Page block](https://doc.ibexa.co/en/latest/content_management/pages/create_custom_page_block/index.md). ### Overwriting existing blocks You can overwrite the following properties in the existing blocks: - `name` - `category` - `thumbnail` - `views` ## Block templates Page blocks can have multiple templates. This allows you to create different styles for each block and let the editor choose them when adding the block from the UI. They names are translatable using the `ibexa_page_builder_block_config` translation domain. ``` ibexa_fieldtype_page: blocks: event: views: default: template: '@ibexadesign/blocks/event/template.html.twig' name: event_block.view.default priority: -255 featured: template: '@ibexadesign/blocks/event/featured_template.html.twig' name: event_block.view.featured priority: 50 ``` `priority` defines the order of block views on the block configuration screen. The highest number shows first on the list. > **Tip: Tip** > > Default views have a `priority` of -255. It's good practice to keep the value between -255 and 255. ### Block modal template The template for the configuration modal of built-in Page blocks is contained in `vendor/ibexa/page-builder/src/bundle/Resources/views/page_builder/block/config.html.twig`. You can override it by using the `configuration_template` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa_fieldtype_page: blocks: event: name: event_block.name category: custom_category.name thumbnail: /bundles/ibexaadminuiassets/vendors/ids-assets/dist/img/all-icons.svg#calendar configuration_template: '@ibexadesign/blocks/event/config.html.twig' ``` The template can extend the default `config.html.twig` and modify its blocks. Blocks `basic_tab_content` and `design_tab_content` correspond to the **Basic** and **Design** tabs in the modal. The following example wraps all form fields for block attributes in an ordered list: ``` {% extends '@IbexaPageBuilder/page_builder/block/config.html.twig' %} {% block basic_tab_content %}
    {{ form_row(form.name) }} {% if attributes_per_category['default'] is defined %}
      {% for identifier in attributes_per_category['default'] %} {% block config_entry %}
    1. {{ form_row(form.attributes[identifier]) }}
    2. {% endblock %} {% endfor %}
    {% endif %}
    {% endblock %} ``` ## Block events To add functionalities to your block that go beyond the available attributes, you can use an event listener. You can listen to events related to block definition and block rendering. The following events are available: - `BlockDefinitionEvents::getBlockDefinitionEventName` - dispatched when block definition is created - `BlockDefinitionEvents::getBlockAttributeDefinitionEventName` - dispatched when block attribute definition is created - `BlockRenderEvents::getBlockPreRenderEventName` - dispatched before a block is rendered - `BlockRenderEvents::getBlockPostRenderEventName` - dispatched after a block is rendered For example, to modify a block by adding a new parameter to it, you can create the following listener: ``` 'onBlockPreRender', ]; } public function onBlockPreRender(PreRenderEvent $event): void { /** @var \Ibexa\FieldTypePage\FieldType\Page\Block\Renderer\Twig\TwigRenderRequest $renderRequest */ $renderRequest = $event->getRenderRequest(); $parameters = $event->getRenderRequest()->getParameters(); $parameters['my_parameter'] = 'parameter_value'; $renderRequest->setParameters($parameters); } } ``` Before the block is rendered, the listener adds `my_parameter` to it with value `parameter_value`. You can use this parameter, for example, in block template: ```
    {{ my_parameter }}
    ``` #### Exposing content relations from blocks Page blocks, for example Embed block or Collection block, can embed other content items. Publishing a page with such blocks creates Relations to those content items. When creating a custom block with embeds, you can ensure such Relations are created using the block Relation collection event. The event is dispatched on content publication. You can hook your event listener to the `BlockRelationEvents::getCollectBlockRelationsEventName` event. To expose relations, pass an array containing Content IDs to the `Ibexa\FieldTypePage\Event\CollectBlockRelationsEvent::setRelations()` method. If embedded Content changes, old Relations are removed automatically. Providing Relations also invalidates HTTP cache for your block response in one of the related content items changes. # Page block attributes A block has attributes that the editor fills in when adding the block to a Page. > **Caution: Clear the persistence cache** > > Persistence cache must be cleared after any modifications have been made to the block config in Page Builder, such as adding, removing or altering the page blocks, block attributes, validators or views configuration. > > To clear the persistence cache run `./bin/console cache:pool:clear [cache-pool]` command. The default cache-pool is named `cache.tagaware.filesystem`. The default cache-pool when running Redis or Valkey is named `cache.redis`. If you have customized the [persistence cache configuration](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/persistence_cache/#what-is-cached), the name of your cache pool might be different. > > In prod mode, you also need to clear the symfony cache by running `./bin/console c:c`. In dev mode, the Symfony cache is rebuilt automatically. Each block can have the following properties: | Attribute | Description | | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `type` | Attribute type. | | `name` | (Optional) The displayed name for the attribute. You can omit it, block identifier is then used as the name. Translatable using the `ibexa_page_builder_block_config` translation domain. | | `value` | (Optional) The default value for the attribute. | | `category` | (Optional) The tab where the attribute is displayed in the block edit modal. | | `validators` | (Optional) [Validators](https://doc.ibexa.co/en/latest/content_management/pages/page_block_validators/index.md) checking the attribute value. | | `options` | (Optional) Additional options, dependent on the attribute type. | ## Block attribute types The following attribute types are available: | Type | Description | Options | | ----------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `integer` | Integer value | - | | `string` | String | - | | `url` | URL | - | | `text` | Text block | - | | `richtext` | Rich text block (see [creating RichText block](https://doc.ibexa.co/en/latest/content_management/rich_text/create_custom_richtext_block/index.md)) | - | | `embed` | Embedded content item | `udw_config_name`: name of the [Universal Discovery Widget's configuration](https://doc.ibexa.co/en/latest/administration/back_office/browser/browser/#add-new-configuration) | | `embedvideo` | Embedded content item | `udw_config_name`: name of the [Universal Discovery Widget's configuration](https://doc.ibexa.co/en/latest/administration/back_office/browser/browser/#add-new-configuration) | | `select` | Drop-down with options to select | - `choices` lists the available options in `label: value` form - `multiple`, when set to true, allows selecting more than one option | | `checkbox` | Checkbox | Selects available option if `value: true`. | | `multiple` | Checkbox(es) | `choices` lists the available options in `label: value` form. | | `radio` | Radio buttons | `choices` lists the available options in `label: value` form. | | `locationlist` | Location selection | `udw_config_name`: name of the [Universal Discovery Widget's configuration](https://doc.ibexa.co/en/latest/administration/back_office/browser/browser/#add-new-configuration) | | `contenttypelist` | List of content types | - | | `schedule_events`,`schedule_snapshots`,`schedule_initial_items`,`schedule_slots`,`schedule_loaded_snapshot` | Used in the Content Scheduler block | - | | `nested_attribute` | Defines a group of attributes in a block. | - `attributes` - a list of attributes in the group. The attributes in the group are [configured](#page-block-attributes) as regular attributes - `multiple`, when set to true. New groups are added dynamically with the **+ Add** button | When you define attributes, you can omit most keys as long as you use simple types that don't require additional options: ``` attributes: first_field: text second_field: string third_field: integer ``` The `embed`, `embedvideo`, and `locationlist` attribute types use the Universal Discovery Widget (UDW). When creating a block with these types you can use the `udw_config_name` option to configure the UDW behavior. See the [custom block example](https://doc.ibexa.co/en/latest/content_management/pages/create_custom_page_block/#configure-block) to learn more. ## Custom attribute types You can create custom attribute type to add to Page blocks. A custom attribute requires attribute type class, a mapper and a template. ### Block attribute type First, create the attribute type class. It can extend one of the types available in `fieldtype-page/src/lib/Form/Type/BlockAttribute/`. You can also use one of the [built-in Symfony types](https://symfony.com/doc/7.4/reference/forms/types.html), for example `AbstractType` for any custom type or `IntegerType` for numeric types. To define the type, create a `src/Block/Attribute/MyStringAttributeType.php` file: ``` create( 'value', MyStringAttributeType::class, [ 'constraints' => $constraints, ] ); } } ``` Then, add a new service definition for your mapper to `config/services.yaml`: ``` App\Block\Attribute\MyStringAttributeMapper: tags: - { name: ibexa.page_builder.form_type_attribute.mapper, alias: my_string } ``` ### Edit templates Next, configure a template for the attribute edit form by creating a `templates/themes/admin/custom_form_templates.html.twig` file: ``` {% block my_string_attribute_widget %}

    My String

    {{ form_widget(form) }} {% endblock %} ``` Add the template to your configuration under the `system..page_builder_forms` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa: system: default: page_builder_forms: block_edit_form_templates: - { template: '@ibexadesign/custom_form_templates.html.twig', priority: 0 } ``` ### Custom attribute configuration Now, you can create a block containing your custom attribute: ``` ibexa_fieldtype_page: blocks: my_block: name: MyBlock category: default thumbnail: /bundles/ibexaadminuiassets/vendors/ids-assets/dist/img/all-icons.svg#edit views: default: name: Default block layout template: '@ibexadesign/blocks/my_block.html.twig' priority: -255 attributes: my_string_attribute: type: my_string name: MyString ``` ### Nested attribute configuration The `nested_attribute` attribute is used when you want to create a group of attributes. First, make sure you have configured the attributes you want to use in the group. Next, provide the configuration. See the example: ``` ibexa_fieldtype_page: blocks: block_name: category: default thumbnail: 'path/icons.svg' views: default: { name: 'Default block layout', template: 'template.html.twig', priority: -255 } attributes: group: name: Group name type: nested_attribute options: attributes: attribute_1: name: Name 1 type: string attribute_2: name: Name 2 type: string multiple: true ``` To set validation for each nested attribute: ``` name: Group name type: nested_attribute options: attributes: attribute_1: name: Name 1 type: string validators: not_blank: message: 'Provide a value' ``` Validators can be also set on a parent attribute (group defining level), it means all validators apply to each nested attribute: ``` name: Group name type: nested_attribute options: attributes: attribute_1: name: Name 1 type: string attribute_2: name: Name 2 type: string multiple: true validators: not_blank: message: 'Provide a value' ``` > **Caution: Moving attributes between groups** > > If you move an attribute between groups or add an ungrouped attribute to a group, the block values are removed. ## Help messages for form fields With the `help`, `help_attr`, and `help_html` field options, you can define help messages for fields in the Page block. You can set options with the following configuration: ``` ibexa_fieldtype_page: blocks: block_name: attributes: attribute_name: options: help: text: 'Some example text' html: true|false attr: class: 'class1 class2' ``` - `help.text` - defines a help message which is rendered below the field (maps to [`help`](https://symfony.com/doc/7.4/reference/forms/types/form.html#help)) - `help.attr` - sets the HTML attributes for the element which displays the help message (maps to [`help_attr`](https://symfony.com/doc/7.4/reference/forms/types/form.html#help-attr)) - `help.html` - enable (default) / disable (set to `true`) escaping the contents of the `help.text` option when rendering in the template (maps to [`help_html`](https://symfony.com/doc/7.4/reference/forms/types/form.html#help-html)) ### Help message in nested attributes You can set the options for root or nested attribute, see the example configuration: ``` ibexa_fieldtype_page: blocks: slider: category: default thumbnail: '/bundles/ibexaadminuiassets/vendors/ids-assets/dist/img/all-icons.svg#edit' views: default: { name: 'Default block layout', template: 'themes/blocks/slider.html.twig', priority: -255 } attributes: group: name: Group name type: nested_attribute options: help: text: 'Root class text' html: true # true|false attr: class: 'root-class-1 root-class-2' attributes: integer: name: Age type: integer validators: not_blank: message: 'Provide a value' options: help: text: 'Nested attribute text' html: true attr: class: 'nested-1 nested-2' string: name: Name type: string validators: not_blank: message: 'Provide a value' ``` *[Image: Help message]* # Page block validators Validators check values passed to Page block attributes. The following block validators are available: - `required` - checks whether the attribute is provided - `regexp` - validates attribute according to the provided regular expression - `not_blank` - checks whether the attribute isn't left empty - `not_blank_richtext` - checks whether a `richtext` attribute isn't left empty - `content_type` - checks whether the selected content types match the provided values - `content_container` - checks whether the selected content item is a container > **Note: Note** > > Don't use the `required` and `not_blank` validators for `richtext` attributes. Instead, use `not_blank_richtext`. For each validator you can provide a message that displays in the Page Builder when an attribute field doesn't fulfill the criteria. Additionally, for some validators you can provide settings under the `ibexa_fieldtype_page.blocks..validators.regexp.options` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files), for example: ``` email: type: string name: E-mail address validators: regexp: options: pattern: '/^\S+@\S+\.\S+$/' message: Provide a valid e-mail address ``` ## Custom validators You can create Page block attributes with custom validators. The following example shows how to create a validator which requires that string attributes contain only alphanumeric characters. First, create classes that support your intended method of validation. For example, in `src/Validator`, create an `AlphaOnly.php` file: ``` context->buildViolation($constraint->message) ->setParameter('{{ string }}', $value) ->addViolation(); } } } ``` Then, under `ibexa_fieldtype_page.block_validators`, enable the new validator in Page Builder: ``` ibexa_fieldtype_page: block_validators: alpha_only: 'App\Validator\AlphaOnly' ``` Finally, add the validator to one of your block attributes, for example: ``` ibexa_fieldtype_page: blocks: my_block: name: My Block category: default thumbnail: /bundles/ibexaadminuiassets/vendors/ids-assets/dist/img/all-icons.svg#edit views: default: name: Default block layout template: '@ibexadesign/blocks/my_block.html.twig' priority: -255 attributes: my_text_attribute: type: text name: My text attribute validators: alpha_only: message: The field can only contain letters or numbers. ``` ### Custom required validator By default, only `not_blank` and `not_blank_richtext` validators mark a block attribute as required. If you create a custom validator `custom_not_blank` with attribute-specific logic, you can extend the `AttributeType` class with a Symfony form type extension to make sure that the attribute is also considered required: ``` getConstraints()['custom_not_blank'])) { $builder->setRequired(true); } } public static function getExtendedTypes(): iterable { return [ AttributeType::class, ]; } } ``` # Create custom Page block In addition to existing blocks which you can use in a Page, you can also create custom blocks. To do this, add block configuration in a YAML file, under the `ibexa_fieldtype_page` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files). > **Caution: Clear the persistence cache** > > Persistence cache must be cleared after any modifications have been made to the block config in Page Builder, such as adding, removing or altering the page blocks, block attributes, validators or views configuration. > > To clear the persistence cache run `./bin/console cache:pool:clear [cache-pool]` command. The default cache-pool is named `cache.tagaware.filesystem`. The default cache-pool when running Redis or Valkey is named `cache.redis`. If you have customized the [persistence cache configuration](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/persistence_cache/#what-is-cached), the name of your cache pool might be different. > > In prod mode, you also need to clear the symfony cache by running `./bin/console c:c`. In dev mode, the Symfony cache is rebuilt automatically. The following example shows how to create a block that showcases an event. ## Configure block First, add the following [YAML configuration](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa_fieldtype_page: blocks: event: name: event_block.name category: custom_category.name thumbnail: /bundles/ibexaadminuiassets/vendors/ids-assets/dist/img/all-icons.svg#calendar attributes: name: type: text name: event_block.name.name validators: not_blank: message: validators.message.event_block.name.validator.not_blank category: type: select name: event_block.category.name value: visual options: multiple: true choices: 'Music': music 'Visual arts': visual 'Sports': sports event: type: embed name: event_block.event.name options: udw_config_name: block_event_embed validators: not_blank: message: validators.message.event_block.embed.validator.not_blank content_type: message: validators.message.event_block.embed.validator.content_type options: types: ['event'] regexp: message: validators.message.event_block.embed.validator.content_item options: pattern: '/[0-9]+/' ``` And provide the translations for the labels: - in `translations/ibexa_page_builder_block_config.en.yaml`: ``` event_block.view.default: Default event_block.view.featured: Featured event_block.name.name: Name event_block.category.name: Category event_block.event.name: Event ``` - in `translations/ibexa_page_fieldtype.en.yaml`: ``` custom_category.name: Custom category event_block.name: Event ``` - in `translations/validators.en.yaml`: ``` validators.message.event_block.name.validator.not_blank: Event name should not be blank. validators.message.event_block.embed.validator.not_blank: Event content should not be blank. validators.message.event_block.embed.validator.content_type: Event content should be of type "event". validators.message.event_block.embed.validator.content_item: Event content should have a numerical ID. ``` `event` is the internal name for the block, and `name` indicates the name under which the block is available in the interface. You also set up the category in the **Page blocks** toolbox that the block appears in. In this case, it doesn't show up with the rest of the built-in blocks, but in a separate "Custom category" category. The thumbnail for the block can be one of the pre-existing icons, like in the example above, or you can use a custom SVG file. A block can have multiple attributes that you edit when adding it to a page. In this example, you configure three attributes: name of the event, category it belongs to, and an event content item that you select and embed. For a list of all available attribute types, see [Page block attributes](https://doc.ibexa.co/en/latest/content_management/pages/page_block_attributes/index.md). Each attribute can have [validators](https://doc.ibexa.co/en/latest/content_management/pages/page_block_validators/index.md). The `not_blank` validators in the example ensure that the user fills in the two block fields. The `content_type` validator in the example ensure that the user choose a content item of the content type `event`. The `regexp` validator ensure that the final value looks like a content ID. The following UDW configuration is used with the `udw_config_name` key so only an event typed content item can be selected: ``` ibexa: system: default: universal_discovery_widget_module: configuration: block_event_embed: multiple: false allowed_content_types: ['event'] ``` For more information, see [UDW configuration](https://doc.ibexa.co/en/latest/administration/back_office/browser/browser/#udw-configuration). ## Add block templates A block can have different templates that you select when adding it to a page. To configure block templates, add them to block configuration: ``` ibexa_fieldtype_page: blocks: event: views: default: template: '@ibexadesign/blocks/event/template.html.twig' name: event_block.view.default priority: -255 featured: template: '@ibexadesign/blocks/event/featured_template.html.twig' name: event_block.view.featured priority: 50 ``` Provide the templates in the indicated folder, in this case in `templates/themes//blocks/event`. For example the `featured_template.html.twig` file can look like this: ```

    {{ name }}

    {{ category }}

    {{ render(controller('ibexa_content::viewAction', { 'contentId': event, 'viewType': 'embed' })) }} ``` The templates have access to all block attributes, as you can see above in the `name`, `category` and `event` variables. Priority of templates indicates the order in which they're presented in Page Builder. The template with the greatest priority is used as the default one. ## Add block JavaScript If your block is animated with JavaScript, you may have to take precaution to keep it working when previewed in back office's Page Builder. If you use an event related to the page being loaded to trigger the initialisation of your custom block, a freshly added block doesn't work in the Page Builder preview. For example, the [`DOMContentLoaded`](https://developer.mozilla.org/en-US/docs/Web/API/Document/DOMContentLoaded_event) event isn't fired when a block is dragged into the page as the DOM is already loaded. The Page Builder fires `body` events that you can listen to initialize your block: - `ibexa-render-block-preview` event is fired when the page is loaded in the Page Builder, when a block is added, when a block is deleted, and when a block setting modification is submitted. - `ibexa-post-update-blocks-preview` event is fired when a block setting modification is submitted, this event has a `detail` property listing the reloaded modified block IDs and their configs. In the following code, the same `initCustomBlocks` function is attached to two event listeners. One listener to call the function when a page is loaded (as a regular front page or as a page edited in the Page Builder). The other one to call it when a block is added or configured in the Page Builder. This `initCustomBlocks` function finds the custom blocks to loop through them, initializes some JavaScript when the block isn't already initialized, and flag the block as initialized. For example, it could initialize carousel blocks with the addition of event listeners to navigation arrows, and the start of an automatic sliding. ``` document.addEventListener('DOMContentLoaded', function(event) { initCustomBlocks(); }); document.getElementsByTagName('body')[0].addEventListener('ibexa-render-block-preview', function(event) { initCustomBlocks(); }); ``` > **Note: Note** > > For the addition of your custom block's JS and CSS files, see [Assets](https://doc.ibexa.co/en/latest/templating/assets/index.md). > > If you consider using React JavaScript library, see [React App block](https://doc.ibexa.co/en/latest/content_management/pages/react_app_block/index.md). ## Add pre-render event listener If you need to compute variables to pass to the template, you can listen or subscribe to the block pre-render event. For example, the following event subscriber loads the `event` content item and passes it to the template as `event_content`: ``` 'onBlockPreRender', ]; } public function onBlockPreRender(PreRenderEvent $event): void { /** @var \Ibexa\FieldTypePage\FieldType\Page\Block\Renderer\Twig\TwigRenderRequest $renderRequest */ $renderRequest = $event->getRenderRequest(); $parameters = $event->getRenderRequest()->getParameters(); $parameters['event_content'] = $this->contentService->loadContent($parameters['event']); $renderRequest->setParameters($parameters); } } ``` The block view template could now use `ibexa_render(event_content, {'viewType': 'embed'})` instead of `render(controller('ibexa_content::viewAction', {'contentId': event, 'viewType': 'embed'}))`, other [content Twig functions](https://doc.ibexa.co/en/latest/templating/twig_function_reference/content_twig_functions/index.md), or [field Twig functions](https://doc.ibexa.co/en/latest/templating/twig_function_reference/field_twig_functions/index.md). For more information, see [Block events](https://doc.ibexa.co/en/latest/content_management/pages/page_blocks/#block-events). ## Add edit template You can also customize the template for the block settings modal. Do this under the `configuration_template` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa_fieldtype_page: blocks: event: name: event_block.name category: custom_category.name thumbnail: /bundles/ibexaadminuiassets/vendors/ids-assets/dist/img/all-icons.svg#calendar configuration_template: '@ibexadesign/blocks/event/config.html.twig' ``` Place the edit template in `templates/themes//blocks/event/config.html.twig`: ``` {% extends '@IbexaPageBuilder/page_builder/block/config.html.twig' %} {% block basic_tab_content %}
    {{ form_row(form.name) }} {% if attributes_per_category['default'] is defined %}
      {% for identifier in attributes_per_category['default'] %} {% block config_entry %}
    1. {{ form_row(form.attributes[identifier]) }}
    2. {% endblock %} {% endfor %}
    {% endif %}
    {% endblock %} ``` Your custom page block is now registered in the system. > **Caution: Caution** > > To use the new block in Page Builder, add it to the list of available blocks in a given content type's settings. This can be done manually in [Page field settings](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/configure_ct_field_settings/#block-display) or by using the migration action [`add_block_to_available_blocks`](https://doc.ibexa.co/en/latest/content_management/data_migration/data_migration_actions/#content-types). # React App block React App block allows an editor to embed a preconfigured React application into a page. It's configured in YAML files, under the `ibexa_fieldtype_page` key. Page block configuration isn't SiteAccess-aware. Another element of React App Block is `\Ibexa\FieldTypePage\FieldType\Page\Block\Event\Listener\ReactBlock` Listener which adds component and props variables. It's common to all the blocks. > **Caution: Clear the persistence cache** > > Persistence cache must be cleared after any modifications have been made to the block config in Page Builder, such as adding, removing or altering the page blocks, block attributes, validators or views configuration. > > To clear the persistence cache run `./bin/console cache:pool:clear [cache-pool]` command. The default cache-pool is named `cache.tagaware.filesystem`. The default cache-pool when running Redis or Valkey is named `cache.redis`. If you have customized the [persistence cache configuration](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/persistence_cache/#what-is-cached), the name of your cache pool might be different. > > In prod mode, you also need to clear the symfony cache by running `./bin/console c:c`. In dev mode, the Symfony cache is rebuilt automatically. ## React App Block configuration React App blocks are regular [Page blocks](https://doc.ibexa.co/en/latest/content_management/pages/page_blocks/index.md) and can be configured on field definition level as any other block. File has exactly the same structure as regular YAML [block configuration](https://doc.ibexa.co/en/latest/content_management/pages/create_custom_page_block/#configure-block), except: - additional `component` attribute which binds Page Builder block with React App - `views` attribute is removed Each configured React app block has an identifier and the following settings: | Setting | Description | | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | Name of the block used in the Page Builder interface. | | `category` | Category in the Page Builder **Page blocks** toolbox that the block is shown in. | | `thumbnail` | Thumbnail used in the Page Builder **Page blocks** toolbox. | | `component` | React App Component name used in `assets/page-builder/react/blocks` directory. | | `visible` | (Optional) Toggles the block's visibility in the Page Builder **Page blocks** toolbox. Remove the block from the layout before you publish another version of the page. | | `attributes` | (Optional) List of [block attributes](https://doc.ibexa.co/en/latest/content_management/pages/page_block_attributes/index.md). | For example: ``` ibexa_fieldtype_page: react_blocks: calculator: name: Calculator category: Demo thumbnail: /bundles/ibexaadminuiassets/vendors/ids-assets/dist/img/all-icons.svg#calendar component: Calculator attributes: a: type: integer b: integer ``` Each entry below `react_blocks` adds one block to the Page Builder with the defined name, category and thumbnail. Both name and attributes support a short syntax and a long one for specifics. `Attributes` defined without sub-keys use the key as the identifier and name, and the value as the type: ``` attributes: b: integer ``` Sub-keys can be used to specify any of the usual [attributes configuration](https://doc.ibexa.co/en/latest/content_management/pages/page_block_attributes/index.md) key: ``` attributes: a: name: Attribute A type: string options: ... ``` Apps that are registered this way must be configured and referenced in the semantic configuration to be registered as blocks. Parameters passed as props must be converted so that they can be used as the configured type in the app. ## Create React App block In the following example, you learn how to create the `Calculator` React App block [configured in the previous section's example](#react-app-block-configuration). ### Configure React App Block First, install React. Run `yarn add react` command. Next, create a .jsx file which describes your component. You can place it in any location. In the following example, create `Calculator.jsx` file in `assets/page-builder/components/` directory: ``` import React from 'react'; export default function (props) { // a + b = ... console.log("Hello React!"); return
    {props.a} + {props.b} = {parseInt(props.a) + parseInt(props.b)}!
    ; } ``` Then, create a `Calculator.js` file in `assets/page-builder/react/blocks` directory. Files in this directory create a map of Components which then are imported to `react.blocks.js` file. As a result, the components are rendered on the page. ``` import Calculator from '/assets/page-builder/components/Calculator'; export default { Calculator: Calculator, }; ``` Now, you should see new `Calculator` block in the Page Builder blocks list: *[Image: Calculator]* Then, make sure that your [Page layout template](https://doc.ibexa.co/en/latest/templating/templates/template_configuration/#page-layout) (like `templates/themes/standard/pagelayout.html.twig`) has the following Twig code in its `{% block javascripts %}`: ``` {% if encore_entry_exists('react-blocks-js') %} {{ encore_entry_script_tags('react-blocks-js') }} {% endif %} ``` # Ibexa Connect scenario block Ibexa Connect scenario block retrieves and displays data from an Ibexa Connect webhook. Scenario block is a regular [Page block](https://doc.ibexa.co/en/latest/content_management/pages/page_blocks/index.md) and can be configured on field definition level as any other block. > **Caution: Caution** > > When setting up your instance, ensure you have profiler enabled. To set up Page Builder in Ibexa DXP, follow the [Page and Form tutorial](https://doc.ibexa.co/en/latest/tutorials/page_and_form_tutorial/page_and_form_tutorial/index.md). ## Scenario block configuration In the following example you can learn how to configure Ibexa Connect scenario block with two available templates: `company_customers` and `external_clients`. ### Block templates First, in `config/packages/ibexa_connect.yaml` add the following configuration: ``` ibexa_connect: scenario_block: block_templates: company_customers: template: 'blocks/default.html.twig' external_clients: label: External clients template: 'blocks/default.html.twig' parameters: external_client_id: string external_client_name: type: string required: true ``` For each block template you can set up additional settings, for example, label, type or parameters. ### Define page layouts To preview your block in the frontend, define page layouts in `config/packages/views.yaml` directory. This file defines, which layouts are used to render Page Builder. ``` ibexa: system: site: page_layout: pagelayout.html.twig user: layout: pagelayout.html.twig ``` You also need to create `pagelayout.html.twig` file in `templates` folder: ``` {% if content is defined %} {% set title = ez_content_name(content) %} {% endif %} {{ title|default('Home'|trans) }} - {{ "It's a Dog's World!"|trans }}
    {% block content %}{% endblock %}
    ``` Then, in `templates/blocks` directory under `default.html.twig`, provide your block configuration: ``` {{ dump(ibexa_connect_data) }} ``` In the following example, the configuration of the block is non-complex - block is only used to display the content transferred from an Ibexa Connect webhook. At this point the Ibexa Connect scenario block is ready to be used in Page Builder. ### Configure Ibexa Connect scenario block in Page Builder Now, you can configure Ibexa Connect scenario block in Page Builder. To do it, in your Page add Ibexa Connect block by dragging it from the menu to a drop zone and enter block settings. - In the **Basic** tab in **Webhook link** field, provide a link to an Ibexa Connect webhook, for example, `https://connect.ibexa.co/3/scenarios/688/edit`: *[Image: Ibexa Connect Basic tab]* - In the **Design** tab, choose one of declared templates, in the following example, `company_customers` or `External clients`. To do it, extend drop-down list in the **View** field and choose one of the available options. *[Image: Ibexa Connect Design tab]* Click **Submit** button to confirm. After submitting the block, page refreshes and Ibexa Connect block displays data from provided Ibexa Connect webhook. *[Image: Ibexa Connect webhook preview]* # Forms Editions: Experience Forms are a type of content item that you can use to improve the functionality of your website. - [Form Builder product guide](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/content_management/forms/form_builder_guide/): See the Form Builder product guide and learn how to create various forms to increase the functionality of your website. - [Forms](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/content_management/forms/work_with_forms/): Form Builder enables creating dynamic forms to use in surveys, questionnaires, sign-up forms and others. - [Form API](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/content_management/forms/form_api/): You can use PHP API to get, create and delete form submissions. - [Create Form Builder Form attribute](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/content_management/forms/create_form_attribute/): Create Form Builder Form attribute. - [Create custom Form field](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/content_management/forms/create_custom_form_field/): Extend a Form with a custom Form field to fit your particular needs. - [Customize email notifications](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/content_management/forms/customize_email_notifications/): Adapt the form and content of emails sent out from the Form Builder. # Form Builder product guide Editions: Experience ## What is Form Builder Form Builder is a tool that lets you build forms consisting of different fields. By adding forms on the website, you can increase its functionality and improve user experience. Use Form Builder to create various forms, such as survey, questionnaire, sign-up form, using basic form fields available in the Form Builder. You can also manage your forms and review the results gathered from the website users. ## Availability Form Builder is available in Ibexa Experience and Ibexa Commerce. ## How does Form Builder work ### Form Builder interface Form Builder user interface consists of: A. Drop zone B. Form fields toolbar C. Save button D. Search bar E. Discard button *[Image: Form Builder interface]* ### Form fields To create forms, you can use available form fields or create custom ones. The available basic form fields are: | Field name | Icon | Description | | ------------------- | ------------------------------ | -------------------------------------------------------------------------- | | Single line input | *[Image: Single line input]* | Single line field for short text. | | Multiple line input | *[Image: Multiple line input]* | Multiple line field for longer text. | | Number | *[Image: Number]* | Field to set up a number using arrows. | | Checkbox | *[Image: Checkbox]* | Single checkbox element with one option value available. | | Checkboxes | *[Image: Checkboxes]* | Multiple checkboxes with more than one option values available. | | Radio | *[Image: Radio]* | List with multiple option values available and visible. | | Dropdown | *[Image: Dropdown]* | Dropdown list with multiple option values available. | | Email | *[Image: Email]* | Field to insert an email address. | | Date | *[Image: Date]* | Field to insert a date. | | URL | *[Image: URL]* | Field to insert an URL address. | | File | *[Image: File]* | Interactive field to upload file. | | Captcha | *[Image: Captcha]* | Field with captcha and additional blank line to rewrite it. | | Button | *[Image: Button]* | Form submit button. | | Hidden field | *[Image: Hidden field]* | Field used to submit metadata that should not be visible in rendered form. | ### Create a form Editors can use the created form anywhere on the website. Forms can be used in page blocks, embedded in the online editor or even used as a field relation. The same form can be placed at multiple locations on the website. To learn more, see [Work with forms](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/work_with_forms/). ### Forms management [Form](https://doc.ibexa.co/en/latest/content_management/forms/work_with_forms/index.md) is one of available [content items](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/content_items/) that you can find in the platform. You can work with it as with other regular items, for example, create new one, edit existing one, or move. You can manage all the existing forms. To do it, in a selected place of the content tree find your form and click on it. In this window you can see all the information about your form, view submissions, create versions, and more Using the buttons in the right corner, you can also edit, move, copy, hide, or send your form to the trash. *[Image: Forms management]* ### Form API To manage form submissions created in the Form Builder, use `FormSubmissionServiceInterface`. You can get existing form submission and create or delete one. Detailed instruction of getting, creating and deleting form submissions, you can find in Ibexa Developer Documentation in [Form API page](https://doc.ibexa.co/en/latest/content_management/forms/form_api/index.md). ### Extend Form Builder You can extend the Form Builder by adding new Form fields or modifying existing ones. To create new form fields, you need to [define them in configuration](https://doc.ibexa.co/en/latest/content_management/forms/create_custom_form_field/index.md). Fields or fields attributes [can be modified](https://doc.ibexa.co/en/latest/content_management/forms/create_custom_form_field/#modify-existing-form-fields) by subscribing `ibexa.form_builder.field.` or `ibexa.form_builder.field..` events. ### Create new Form attribute Each Form has available attributes, for example, string, text, or location. You can also [create a Form attribute](https://doc.ibexa.co/en/latest/content_management/forms/create_form_attribute/index.md) for new Form fields or existing ones. To do it, you have to: 1. define a new Form attribute in the configuration, 2. create a mapper, 3. add Symfony form type, 4. customize Form templates, 5. add scripts, 6. implement field, 7. implement field mapper, 8. create submission converter. ### View results You can preview the results of each published form. To do it, go to **Submissions** tab in the content item view: *[Image: View results]* Here you can view the details of each submission or delete any of them. The **Download submissions** button enables you to download all the submissions in a .CSV (comma-separated value) file. ## Benefits ### General overview With Form Builder you're allowed to build an unlimited number of forms. These forms can be used anywhere on the website and are ready to start collecting information. Form Builder interface is plain, which makes the creation of forms fast and intuitive. ### Forms management Forms can be managed simply and effectively: you can copy them, move, organize into folders, create versions, and delete if necessary. Each field can be configured so that the form collects the exact details that you need. ### Custom Form fields With Form Builder you can use existing Form fields, but also you can extend it by adding new or modifying existing ones. This allows you to create forms that fit your needs. ### Analytic tool All the submissions can are visible in **Submissions** tab. You can download them as a .CSV file for additional analysis. # Forms Editions: Experience You can build forms consisting of different fields in the Form Builder. > **Tip: Tip** > > To learn how to get, create, and delete form submissions by using the PHP API, see [Form API](https://doc.ibexa.co/en/latest/content_management/forms/form_api/index.md). > **Caution: Known limitation** > > To have multiple instances of the same form on one page, create several identical form blocks. Otherwise, you may encounter issues with submitting data from all forms at the same time. ## Existing Form fields ### Captcha field The Captcha Form field is based on [Gregwar/CaptchaBundle](https://github.com/Gregwar/CaptchaBundle). *[Image: Captcha field]* You can customize the field by adding configuration to `config/packages/gregwar_captcha.yaml` under `gregwar_captcha`: ``` gregwar_captcha: as_url: true width: 150 invalid_message: Code does not match, please retry. reload: true ``` The example configuration above resizes the Captcha image (line 3), changes the error message (line 4), and enables the user to reload the code (line 5). *[Image: Custom captcha field]* For information about available options, see [Gregwar/CaptchaBundle's documentation](https://github.com/Gregwar/CaptchaBundle#options). > **Note: Note** > > If your installation uses Varnish to manage content cache, you must modify the configuration to avoid issues with the Captcha field. For more information, see [Ensure proper captcha behavior](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/http_cache/reverse_proxy/#ensure-proper-captcha-behavior). ## Form submission purging You can purge all submissions of a given form. To do this, run the following command, where `form-id` stands for Content ID of the form for which you want to purge data: ``` php bin/console ibexa:form-builder:purge-form-submissions [options] [--] ``` The following table lists some of the available options and their meaning: | Switch | Option | Description | | ------ | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | | `-l` | `--language-code=LANGUAGE-CODE` | Passes a language code, for example, "eng-GB". | | `-u` | `--user[=USER]` | Passes a repository username. By default it's "admin". | | `-c` | `--batch-size[=BATCH-SIZE]` | Passes a number of URLs to check in a single iteration. Set it to avoid using too much memory. By default it's set to 50. | | | `--siteaccess[=SITEACCESS]` | Passes a SiteAccess to use for operations. If not provided, the default SiteAccess is used. | ## Form-uploaded files You can use Forms to enable the user to upload files. The default location for files uploaded in this way is `/Media/Files/Form Uploads`. You can change it with the following configuration: ``` ibexa: system: default: form_builder: upload_location_id: 54 ``` This applies only if no specific location is defined in the Form itself. # Form API Editions: Experience ## Form submissions To manage form submissions created in the [Form Builder](https://doc.ibexa.co/en/latest/content_management/forms/form_builder_guide/index.md), use `FormSubmissionServiceInterface`. ### Getting form submissions To get existing form submissions, use `FormSubmissionServiceInterface::loadByContent()` (which takes a `ContentInfo` object as parameter), or `FormSubmissionServiceInterface::loadById()`. ``` $submissions = $this->formSubmissionService->loadByContent($contentInfo); ``` Through this object, you can get information about submissions, such as their total number, and submission contents. ``` $output->writeln('Total number of submissions: ' . $submissions->getTotalCount()); foreach ($submissions as $sub) { $output->write($sub->getId() . '. submitted on '); $output->write($sub->getCreated()->format('Y-m-d H:i:s') . ' by '); $output->writeln((string) $this->userService->loadUser($sub->getUserId())->getName()); foreach ($sub->getValues() as $value) { $output->writeln('- ' . $value->getIdentifier() . ': ' . $value->getDisplayValue()); } } ``` ### Creating form submissions To create a form submission, use the `FormSubmissionServiceInterface::create()` method. This method takes: - the `ContentInfo` object of the content item containing the form - the language code - the value of the field containing the form - the array of form field values ``` $formValue = $content->getFieldValue('form', 'eng-GB')->getFormValue(); $data = [ ['id' => 7, 'identifier' => 'single_line', 'name' => 'Line', 'value' => 'The name'], ['id' => 8, 'identifier' => 'number', 'name' => 'Number', 'value' => 123], ['id' => 9, 'identifier' => 'checkbox', 'name' => 'Checkbox', 'value' => 0], ]; $this->formSubmissionService->create( $contentInfo, 'eng-GB', $formValue, $data ); ``` ### Deleting form submissions You can delete a form submission by using the `FormSubmissionServiceInterface::delete()` method. ``` $submission = $this->formSubmissionService->loadById(29); $this->formSubmissionService->delete($submission); ``` # Create custom Form field Editions: Experience You can extend the Form Builder by adding new Form fields or modifying existing ones. Define new form fields in configuration. ## Configure Form field For example, to create a Country Form field in the "Custom form fields" category, provide the following configuration under the `ibexa_form_builder.fields` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa_form_builder: fields: country: name: country_field.name category: custom_category.name thumbnail: '/bundles/ibexaadminuiassets/vendors/ids-assets/dist/img/all-icons.svg#pins-locations' attributes: label: name: country_field.label.name type: string validators: not_blank: message: You must provide a label for the field help: name: country_field.help.name type: string validators: required: ~ ``` and provide the translations for the labels in `translations/ibexa_form_builder.en.yaml`: ``` country_field.name: Country custom_category.name: Custom form fields country_field.label.name: Display label country_field.help.name: Help text ``` Available attribute types are: | Type | Description | | ---------- | ------------------------- | | `string` | String | | `text` | Text block | | `integer` | Integer number | | `url` | URL | | `multiple` | Multiple choice | | `select` | Dropdown | | `checkbox` | Checkbox | | `location` | Content location | | `radio` | Radio button | | `action` | Button | | `choices` | List of available options | Each type of Form field can have validators of the following types: - `required` - `min_length` - `max_length` - `min_choices` - `max_choices` - `min_value` - `max_value` - `regex` - `upload_size` - `extensions` ## Create mapper New types of fields require a mapper which implements the `Ibexa\Contracts\FormBuilder\FieldType\Field\FieldMapperInterface` interface. To create a Country field type, implement the `FieldMapperInterface` interface in `src/FormBuilder/Field/Mapper/CountryFieldMapper.php`: ``` getAttributeValue('label'); $options['help'] = $field->getAttributeValue('help'); return $options; } } ``` Then, register the mapper as a service: ``` services: App\FormBuilder\Field\Mapper\CountryFieldMapper: arguments: $fieldIdentifier: country $formType: Symfony\Component\Form\Extension\Core\Type\CountryType tags: - { name: ibexa.form_builder.field.mapper } ``` Now you can go to back office and build a new form. You should be able to see the new section in the list of available fields: *[Image: Custom form fields]* And a new Country Form field: *[Image: Country field]* ## Modify existing Form fields Field or field attribute definition can be modified by subscribing to one of the following events: - `ibexa.form_builder.field.` - `ibexa.form_builder.field..` The following example adds a `custom` string attribute to `single_line` field definition. ``` 'onSingleLineFieldDefinition', ]; } public function onSingleLineFieldDefinition(FieldDefinitionEvent $event): void { $isReadOnlyAttribute = new FieldAttributeDefinitionBuilder(); $isReadOnlyAttribute->setIdentifier('custom'); $isReadOnlyAttribute->setName('Custom attribute'); $isReadOnlyAttribute->setType('string'); $definitionBuilder = $event->getDefinitionBuilder(); $definitionBuilder->addAttribute($isReadOnlyAttribute->buildDefinition()); } } ``` Register this subscriber as a service: ``` services: App\EventSubscriber\FormFieldDefinitionSubscriber: public: true tags: - kernel.event_subscriber ``` ## Access Form field definitions Field definitions are accessible through: - `Ibexa\FormBuilder\Definition\FieldDefinitionFactory` in the back end - global variable `ibexa.formBuilder.config.fieldsConfig` in the front end # Create Form Builder Form attribute Editions: Experience You can create a Form attribute for new Form fields or existing ones. To do it, you have to define a new Form attribute in the configuration. In the following example you can learn how to create the new Form with `richtext_description` attribute that allows you to add formatted description to the Form. ## Configure Form attribute To create a `richtext_description` attribute, add the following configuration under the `ibexa_form_builder.fields` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa_form_builder: fields: checkbox_with_richtext_description: name: Checkbox with Rich Text description category: Default thumbnail: '/bundles/ibexaadminuiassets/vendors/ids-assets/dist/img/all-icons.svg#form-input-single-line' attributes: label: name: Label type: string validators: not_blank: message: You must provide label of the field richtext_description: name: 'Description' type: 'richtext_description' validators: required: ~ ``` ## Create mapper The new Form attribute requires a `FieldAttributeTypeMapper`. Register the mapper as a service in `config/services.yaml`: ``` App\FormBuilder\FieldType\Field\Mapper\CheckboxWithRichtextDescriptionFieldMapper: arguments: $fieldIdentifier: checkbox_with_richtext_description $formType: 'App\FormBuilder\Form\Type\CheckboxWithRichtextDescriptionType' tags: - { name: ibexa.form_builder.field.mapper } ibexa.form_builder.attribute_form_type_mapper.richtext_description: class: Ibexa\FormBuilder\Form\Mapper\FieldAttribute\GenericFieldAttributeTypeMapper arguments: $formTypeClass: App\FormBuilder\Form\Type\FieldAttribute\AttributeRichtextDescriptionType $typeIdentifier: 'richtext_description' tags: - { name: ibexa.form_builder.form.type.attribute.mapper } App\FormBuilder\FormSubmission\Converter\RichtextDescriptionFieldSubmissionConverter: arguments: $typeIdentifier: 'checkbox_with_richtext_description' $twig: '@twig' tags: - { name: ibexa.form_builder.field.submission.converter } ``` ## Add Symfony form type The attribute must be editable for the form creator, so it needs to have a Symfony form type. Add an `AttributeRichtextDescriptionType.php` file with the form type in the `src/FormBuilder/Form/Type/FieldAttribute` directory: ``` {% set udw_context = { 'languageCode': 'en', } %} {{ form_errors(form) }} {{ form_row(form) }} {{ encore_entry_script_tags('formbuilder-richtext-checkbox-js') }} {% endblock %} ``` - `templates/themes//formtheme/formbuilder_checkbox_with_richtext_description.html.twig`: ``` {% block checkbox_with_richtext_description_row %} {{ form_label(form)}} {{ form_errors(form) }} {{ form_widget(form) }} {{ form.vars.richtextDescription|ibexa_richtext_to_html5() }} {% endblock %} ``` Then, specify the new template in configuration, under the `twig.form_themes` configuration key: ``` twig: form_themes: - '@ibexadesign/formtheme/formbuilder_checkbox_with_richtext_description.html.twig' ``` ## Add scripts Now you need to enable the Rich Text editor. Provide the required script in a new `assets/js/formbuilder-richtext-checkbox.js` file: ``` (function (global, doc, ibexa) { global.addEventListener('load', (event) => { const richtext = new ibexa.BaseRichText(); // Enable editor in all ibexa-data-source divs doc.querySelectorAll('.ibexa-data-source').forEach((ibexaDataSource) => { const richtextContainer = ibexaDataSource.querySelector('.ibexa-data-source__richtext'); if (richtextContainer.classList.contains('ck')) { return; } richtext.init(richtextContainer); }); }); const openUdw = (config) => { const openUdwEvent = new CustomEvent('ibexa-open-udw', { detail: config }); doc.body.dispatchEvent(openUdwEvent); }; ibexa.addConfig('richText.alloyEditor.callbacks.selectContent', openUdw); })(window, window.document, window.ibexa); ``` Then, paste the highlighted part of the code into the `webpack.config.js` file: ``` const Encore = require('@symfony/webpack-encore'); const path = require('path'); const getIbexaConfig = require('./ibexa.webpack.config.js'); const ibexaConfig = getIbexaConfig(Encore); const customConfigs = require('./ibexa.webpack.custom.configs.js'); const { isReactBlockPathCreated } = require('./ibexa.webpack.config.react.blocks.js'); Encore.reset(); Encore .setOutputPath('public/build/') .setPublicPath('/build') .enableStimulusBridge('./assets/controllers.json') .enableSassLoader() .enableReactPreset() .enableSingleRuntimeChunk() .copyFiles({ from: './assets/images', to: 'images/[path][name].[ext]', pattern: /\.(png|svg)$/ }) .configureBabel((config) => { config.plugins.push('@babel/plugin-proposal-class-properties'); }) // enables @babel/preset-env polyfills .configureBabelPresetEnv((config) => { config.useBuiltIns = 'usage'; config.corejs = 3; }) ; // Welcome page stylesheets Encore.addEntry('welcome-page-css', [ path.resolve(__dirname, './assets/scss/welcome-page.scss'), ]); // Welcome page javascripts Encore.addEntry('welcome-page-js', [ path.resolve(__dirname, './assets/js/welcome.page.js'), ]); if (isReactBlockPathCreated) { // React Blocks javascript Encore.addEntry('react-blocks-js', './assets/js/react.blocks.js'); } Encore.addEntry('app', './assets/app.js'); Encore.addEntry('formbuilder-richtext-checkbox-js', './assets/js/formbuilder-richtext-checkbox.js'); const projectConfig = Encore.getWebpackConfig(); projectConfig.name = 'app'; module.exports = [ibexaConfig, ...customConfigs, projectConfig]; // uncomment this line if you've commented-out the above lines // module.exports = [ eZConfig, ibexaConfig, ...customConfigs ]; ``` Clear the cache and regenerate the assets by running the following commands: ``` php bin/console cache:clear php bin/console assets:install yarn encore dev ``` ## Implement field Now you have to implement the field, and make sure the value from the Rich Text attribute is passed on to the field form. Create a `src/FormBuilder/Form/Type/CheckboxWithRichtextDescriptionType.php` file. ``` setDefaults([ 'richtext_description' => '', ]); $resolver->setAllowedTypes('richtext_description', ['null', 'string']); } public function buildView(FormView $view, FormInterface $form, array $options): void { // pass the Dom object of the richtext doc to the template $dom = new \DOMDocument(); if (!empty($options['richtext_description'])) { $dom->loadXML($options['richtext_description']); } $view->vars['richtextDescription'] = $dom; } } ``` ## Implement field mapper To implement a field mapper, create a `src/FormBuilder/FieldType/Field/Mapper/CheckboxWithRichtextDescriptionFieldMapper.php` file. ``` getAttributeValue('label'); $options['richtext_description'] = $field->getAttributeValue('richtext_description'); return $options; } } ``` Now, the attribute value can be stored in the new Form. ## Create submission converter The new field is based on a checkbox, so to display the submissions of this field, you can use the `BooleanFieldSubmissionConverter`. Create a `src/FormBuilder/FormSubmission/Converter/RichtextDescriptionFieldSubmissionConverter.php` file. ``` **Forms** -> **Create content**, and select **Form**. You should be able to see the new section in the list of available fields: *[Image: New form field]* When editing settings, the "Description" attribute has the Rich Text input. *[Image: Field settings]* When you enter the "Description" attribute, the Rich Text toolbar appears. *[Image: Rich Text toolbar]* The preview displays the formatted text along with the checkbox and its label. *[Image: Field preview]* # Customize email notifications Editions: Experience Email is one of the **Submit** button options you can add to a form in the Form Builder. Use it to configure a list of email addresses that get notifications about newly filled forms. *[Image: Email notification]* ## Override email template To customize the form submission email, override the `form_builder/form_submit_notification_email.html.twig` template. It contains two blocks: `subject` and `body`. Each of them is rendered independently and consists of three sets of parameters. | Parameter | Type | Description | | --------- | ------------------------------------------------------------ | ---------------------------------- | | `content` | `Ibexa\Contracts\Core\Repository\Values\Content\Content` | Name of the form, its content type | | `form` | `Ibexa\Contracts\FormBuilder\FieldType\Model\Form` | Definition of the form | | `data` | `Ibexa\Contracts\FormBuilder\FieldType\Model\FormSubmission` | Sent data | ## Configure sender details Some email providers require a sender address to be set, so to avoid unsent emails when using Form Builder, it's recommended to configure `sender_address` in `config/packages/swiftmailer.yaml`. This email acts as a sender and return address for all bounced messages. > **Note: Note** > > Since November 2021 the Swift Mailer is no longer supported and the integration with Symfony is deprecated in Symfony 6.0. The Swift Mailer got replaced by the Symfony Mailer. Add `sender_address` entry to `config/packages/swiftmailer.yaml`: ``` swiftmailer: url: '%env(MAILER_URL)%' spool: { type: 'memory' } sender_address: '%env(MAILER_SENDER_ADDRESS)%' ``` In the `.env` file, define a new environment variable: `MAILER_SENDER_ADDRESS=mail@example.com` and configure your mail server connection details in the `MAILER_URL` environmental variable. # Workflow The workflow functionality passes a content item version through a series of stages. For example, an editorial workflow can pass a content item from draft stage through design and proofreading. By default, Ibexa DXP comes pre-configured with a Quick Review workflow. You can disable the default workflow and define different workflows in configuration. Workflows are permission-aware. ## Workflow configuration Each workflow consists of stages and transitions between them. The following example configuration defines a workflow where you can optionally pass a draft to be checked by the legal team. *[Image: Diagram of custom workflow]* ``` ibexa: system: default: workflows: custom_workflow: name: Custom Workflow matchers: content_type: [article, folder] content_status: [draft] stages: draft: label: Draft color: '#f15a10' legal: label: Legal color: '#5a10f1' actions: notify_reviewer: ~ done: label: Done color: '#301203' last_stage: true initial_stage: draft transitions: to_legal: from: [draft] to: [legal] label: To legal color: '#8888ba' icon: '/bundles/ibexaadminuiassets/vendors/ids-assets/dist/img/all-icons.svg#alert-error' reviewers: required: true user_group: 13 back_to_draft: reverse: to_legal label: Back to draft color: '#cb8888' icon: '/bundles/ibexaadminuiassets/vendors/ids-assets/dist/img/all-icons.svg#arrow-left' approved_by_legal: from: [legal] to: [done] label: Approved by legal color: '#88ad88' icon: '/bundles/ibexaadminuiassets/vendors/ids-assets/dist/img/all-icons.svg#form-checkbox' actions: publish: ~ done: from: [draft] to: [done] label: Done color: '#88ad88' icon: '/bundles/ibexaadminuiassets/vendors/ids-assets/dist/img/all-icons.svg#form-checkbox' actions: publish: ~ ``` ### Matchers Matchers define when the workflow is used. Their configuration is optional. `content_type` contains an array of content type identifiers that use this workflow. `content_status` lists the statuses of content items which fall under this workflow. The available values are: `draft` and `published`. If set to `draft`, applies for new content (newly created). If set to `published`, applies for content that has already been published (for example, edit after the content was published). ``` matchers: content_type: [article, folder] content_status: [draft] ``` ### Stages Each stage in the workflow has an identifier and can have a label and a color. The optional `last_stage` key indicates that content in this stage doesn't appear on the dashboard or in Review Queue. One stage, listed under `initial_stage`, is the one that the workflow starts with. ``` stages: draft: label: Draft color: '#f15a10' legal: label: Legal color: '#5a10f1' actions: notify_reviewer: ~ done: label: Done color: '#301203' last_stage: true initial_stage: draft ``` ### Transitions Each transition has an identifier and can have a label, a color, and an icon. A transition must state between which stages it transitions (lines 3-4), or be `reverse` to a different transition (line 9). ``` transitions: to_legal: from: [draft] to: [legal] label: To legal color: '#8888ba' icon: '/bundles/ibexaadminuiassets/vendors/ids-assets/dist/img/all-icons.svg#alert-error' back_to_draft: reverse: to_legal label: Back to draft color: '#cb8888' icon: '/bundles/ibexaadminuiassets/vendors/ids-assets/dist/img/all-icons.svg#arrow-left' ``` ### Reviewers When moving a content item through a transition, the user can select a reviewer. Assigning a reviewer is mandatory if you set `reviewers.required` to `true` for this transition. You can restrict who can review the content item by setting `reviewers.user_group` to a location ID of the user group. To be able to search for users for review, the user must have the `content/read` policy without any limitation, or with a limitation that allows reading users. This means that, in addition to your own settings for this policy, you must add the /Users subtree to the limitation and add users in the [content type limitation](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#content-type-limitation). ``` transitions: to_legal: from: [draft] to: [legal] label: To legal color: '#8888ba' icon: '/bundles/ibexaadminuiassets/vendors/ids-assets/dist/img/all-icons.svg#alert-error' reviewers: required: true ``` #### Notifications To ensure that the assigned reviewers get a notification of a transition, configure the `actions.notify_reviewer` action for a stage. ``` legal: label: Legal color: '#5a10f1' actions: notify_reviewer: ~ ``` The notification is displayed in the user menu: *[Image: Notification about content to review]* #### Draft locking You can configure draft assignment in a way that when a user sends a draft to review, only the first editor of the draft can either edit the draft or unlock it for editing, and no other user can take it over. Use the [Version Lock limitation](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#version-lock-limitation), set to "Assigned only", together with the `content/edit` and `content/unlock` policies to prevent users from editing and unlocking drafts that are locked by another user. ### Content publishing You can automatically publish a content item once it goes through a specific transition. To do so, configure the `publish` action for the transition: ``` done: from: [draft] to: [done] label: Done color: '#88ad88' icon: '/bundles/ibexaadminuiassets/vendors/ids-assets/dist/img/all-icons.svg#form-checkbox' actions: publish: ~ ``` ### Disable Quick Review You can disable the default workflow, for example, if your project doesn't use workflows, or Quick Review entries clog your database: ``` ibexa: system: default: workflows: quick_review: name: Quick Review matchers: content_type: [] ``` ## Custom actions Besides the built-in actions of publishing content and notifying the reviewers, you can also [create custom workflow actions](https://doc.ibexa.co/en/latest/content_management/workflow/add_custom_workflow_action/index.md). ## Workflow event timeline Workflow event timeline displays workflow transitions. You can also use it to render custom entries in the timeline, for example system alerts on workflows. ### Custom entry type To add a custom entry type, create a custom class extending `Ibexa\Workflow\WorkflowTimeline\Value\AbstractEntry`. Use an `Ibexa\Contracts\Workflow\Event\TimelineEvents::COLLECT_ENTRIES` event to add your entries to the timeline. ### Custom templates To provide custom templates for new event timeline entries, use the following configuration: ``` ibexa: system: default: workflows_config: timeline_entry_templates: - { template: '@IbexaWorkflow/ibexa_workflow/timeline/entries.html.twig', priority: 10 } ``` The template has to provide a block named `ez_workflow_timeline_entry_{ENTRY_IDENTIFIER}`. ## Permissions You can limit access to workflows at stage and transition level. The `workflow/change_stage` policy grants permission to change stages in a specific workflow. You can limit this policy with the [Workflow Transition limitation](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#workflow-transition-limitation) to only allow sending content in the selected transition. For example, by using the example above, a `workflow/change_stage` policy with `WorkflowTransitionLimitation` set to `Approved by legal` allows a legal team to send content forward after they're done with their review. You can also use the [Workflow Stage Limitation](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#workflow-stage-limitation) together with the `content/edit` and `content/publish` Policies to limit the ability to edit content in specific stages. For example, you can use it to only allow a legal team to edit content in the `legal` stage. ## Validation ### Validate form before workflow transition By default, sending content to the next stage of the workflow doesn't validate the form in UI, so with the publish action, the form isn't verified for errors in UI. However, during the publish action, the sent form is validated in the service. Therefore, if there are any errors in the form, you return to the edit page but errors aren't triggered, which can be confusing when you have two or more tabs. To enable form validation in UI before sending it to the next stage of the workflow, add `validate: true` to the transitions of the stage. In the example below the form is validated in two stages:`to_legal` and `done`: ``` transitions: to_legal: from: [draft] to: [legal] label: To legal color: '#8888ba' icon: '/bundles/ibexaadminuiassets/vendors/ids-assets/dist/img/all-icons.svg#alert-error' reviewers: required: true user_group: 13 actions: legal_transition_action: data: message: "Sent to the legal department" validate: true back_to_draft: reverse: to_legal label: Back to draft color: '#cb8888' icon: '/bundles/ibexaadminuiassets/vendors/ids-assets/dist/img/all-icons.svg#arrow-left' from: [draft] to: [done] label: Done color: '#88ad88' icon: '/bundles/ibexaadminuiassets/vendors/ids-assets/dist/img/all-icons.svg#form-checkbox' actions: publish: ~ validate: true ``` You can check validation for a particular stage of the workflow even if the stage doesn't have any actions. # Workflow API You can manage [workflows](https://doc.ibexa.co/en/latest/content_management/workflow/workflow/index.md) with PHP API by using `WorkflowServiceInterface`. ## Workflow service Workflow uses the Symfony [Workflow Component](https://symfony.com/doc/7.4/components/workflow.html), extended in the workflow service. The service implements the following methods: - `start` - places a content item in a workflow - `apply` - performs a transition - `can` - checks if a transition is possible The methods `apply` and `can` are the same as in Symfony Workflow, but the implementation in workflow service extends them, for example by providing messages. ## Getting workflow information To get information about a specific workflow for a content item, use `WorkflowServiceInterface::loadWorkflowMetadataForContent`: ``` $workflowMetadata = $this->workflowService->loadWorkflowMetadataForContent($content, $workflowName); foreach ($workflowMetadata->markings as $marking) { $output->writeln($content->getName() . ' is in stage ' . $marking->name . ' in workflow ' . $workflowMetadata->workflow->getName()); } ``` > **Tip: Tip** > > `marking`, a term from [Symfony Workflow](https://symfony.com/doc/7.4/components/workflow.html), refers to a state in a workflow. To get a list of all workflows that can be used for a given content item, use `WorkflowRegistry`: ``` $supportedWorkflows = $this->workflowRegistry->getSupportedWorkflows($content); foreach ($supportedWorkflows as $supportedWorkflow) { $output->writeln('Supports workflow: ' . $supportedWorkflow->getName()); } ``` ## Applying workflow transitions To place a content item in a workflow, use `WorkflowService::start`: ``` $this->workflowService->start($content, $workflowName); ``` To apply a transition to a content item, use `Workflow::apply`. Additionally, you can check if the transition is possible for the given object using `WorkflowService::can`: ``` if ($this->workflowService->can($workflowMetadata, $transitionName)) { $workflow = $this->workflowRegistry->getWorkflow($workflowName); $workflow->apply($workflowMetadata->content, $transitionName, ['message' => 'done', 'reviewerId' => 14]); $output->writeln('Moved ' . $content->getName() . ' through transition ' . $transitionName); } ``` > **Tip: Tip** > > `Ibexa\Workflow\Value\WorkflowMetadata` object contains all information about a workflow, such as ID, name, transitions and current stage. `Ibexa\Workflow\Value\WorkflowMetadata::$workflow` gives you direct access to native Symfony Workflow object. # Add custom workflow action Built-in workflow actions enable you to [automatically publish a content item](https://doc.ibexa.co/en/latest/content_management/workflow/workflow/#content-publishing) or to [send a notification to reviewers](https://doc.ibexa.co/en/latest/content_management/workflow/workflow/#notifications). You can also create custom actions that are called when content reaches a specific stage or goes through a transition in a workflow. The following example shows how to configure two custom actions that send customized notifications. ## Configure custom action Configure the first custom action under the `ibexa.system..workflows` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa: system: default: workflows: custom_workflow: transitions: to_legal: from: [draft] to: [legal] label: To legal color: '#8888ba' icon: '/bundles/ibexaadminuiassets/vendors/ids-assets/dist/img/all-icons.svg#alert-error' reviewers: required: true user_group: 13 actions: legal_transition_action: data: message: "Sent to the legal department" ``` The configuration indicates the name of the custom action (`legal_transition_action`). `data` contains additional data that is passed to the action. In this case, it's a message to display. ## Create event listener To define what the action does, create an event listener `src/EventListener/LegalTransitionListener.php`: ``` getActionMetadata($event->getWorkflow(), $event->getTransition()); $message = $metadata['data']['message'] ?? ''; $this->notificationHandler->info( $message, [], 'domain' ); $this->setResult($event, true); } } ``` This listener displays a notification bar at the bottom of the page when a content item goes through the `to_legal` transition. The content of the notification is the message configured in `actions.legal_transition_action.data`. To get it, access the metadata for this transition through `getActionMetadata()` (line 27). Register the listener as a service (in `config/services.yaml`): ``` services: App\EventListener\LegalTransitionListener: tags: - { name: ibexa.workflow.action.listener } ``` ## Use custom transition value Line 36 in the listener above sets a custom result value for the transition. You can use this value in other stages and transitions for this content item, for example: ``` approved_by_legal: from: [legal] to: [done] label: Approved by legal color: '#88ad88' icon: '/bundles/ibexaadminuiassets/vendors/ids-assets/dist/img/all-icons.svg#form-checkbox' actions: publish: ~ approved_transition_action: condition: - result.legal_transition_action == true ``` The action indicated here is performed only if the result from the `legal_transition_action` is set to `true`. Then, the following `src/EventListener/ApprovedTransitionListener` is called: ``` getContext(); $message = $context['message']; $this->notificationHandler->info( $message, [], 'domain' ); } } ``` Register this listener as a service: ``` services: App\EventListener\ApprovedTransitionListener: tags: - { name: ibexa.workflow.action.listener } ``` This listener also displays a notification, but in this case its content is taken from the message that the user types when choosing the `Done` transition. The message is contained in the context of the action. `$event->getContext()` (line 27) gives you access to the context. The context contains: - `$workflowId` - the ID of the current workflow - `$message` - content of the user's message when sending the content item through the transitions - `$reviewerId` - ID of the user who was selected as a reviewer - `$result` - an array of transition actions performed so far You can also modify the context using the `setContext()` method. For example, you can override the message typed by the user: ``` $new_context = $context; $new_context['message'] = "This article went through proofreading"; $event->setContext($new_context); ``` # URL management You can manage external URL addresses and URL wildcards in the back office, **Admin** tab, the **URL Management** node. Configure URL aliases to have human-readable URL addresses throughout your system. ## Link manager When developing a site, users can enter links to external websites in either RichText or URL fields. Each such link is then displayed in the URL table. You can view and update all external links that exist within the site, without having to modify and re-publish the individual content items. The **Link manager** tab contains all the information about each link, including its status (valid or invalid) and the time the system last attempted to validate the URL address. Click an entry in the list to display its details and check which content items use this link. Edit the entry to update the URL address in all the occurrences throughout the website. > **Note: Note** > > When you edit the details of an entry to update the URL address, the status automatically changes to valid. ## External URL validation You can validate all the addresses from the URL table by executing the `ibexa:check-urls` command. It validates the links by accessing them one by one and updates the value in the Last checked field. If a broken link is found, its status is set to "invalid". The following protocols are currently supported: - `http` - `https` - `mailto` ### Enabling automatic URL validation To enable automatic URL validation, set up cron to run the `ibexa:check-urls` command periodically. For example, to check links every week, add the following script: ``` echo '0 0 * * 0 cd [path-to-ibexa]; php bin/console ibexa:check-urls --quiet --env=prod' > ezp_cron.txt ``` Next, append the new cron to user's crontab without destroying existing crons. Assuming that the web server user data is www-data: ``` crontab -u www-data -l|cat - ezp_cron.txt | crontab -u www-data - ``` Finally, remove the temporary file: ``` rm ezp_cron.txt ``` ### Configuration The configuration of external URLs validation is SiteAccess-aware and is stored under the `ibexa.system..url_checker` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files), for example: ``` ibexa: system: default: url_checker: handlers: http: enabled: true batch_size: 64 https: enabled: true ignore_certificate: false mailto: enabled: false ``` Available options are protocol-specific. For details, see the tables below. #### http/https protocol | Option | Description | Default value | | ------------------ | -------------------------------------------------------------------------------------------- | ------------- | | enabled | Enables link validation. | true | | timeout | Defines the time that the request is allowed to take (in seconds). | 10 | | connection_timeout | Defines the time that the connect phase is allowed to take (in seconds). | 5 | | batch_size | Defines a maximum number of asynchronous requests. | 10 | | ignore_certificate | Decides if the peer's SSL certificate or the certificate name are verified against the host. | false | #### mailto protocol | Option | Description | Default value | | ------- | ------------------------ | ------------- | | enabled | Enables link validation. | true | For more information about Ibexa configuration, see [Configuration](https://doc.ibexa.co/en/latest/administration/configuration/configuration/index.md). ### Custom protocol support You can extend the external URL address validation with a custom protocol. To do this, you must provide a service that implements the `Ibexa\Bundle\Core\URLChecker\URLHandlerInterface` interface: s ``` **Note: Note** > > Make sure that you correctly define languages used by the site in the configuration (under the `ibexa.system..languages` key). Otherwise, redirections for the renamed Content with translations in multiple languages may fail to work properly. > **Caution: Legacy storage engine limitation** > > The [Legacy storage engine](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_storage/#legacy-storage-engine) doesn't archive URL aliases, which initially had the same name in multiple languages. URL aliases aren't SiteAccess-aware. When creating an alias, you can select a SiteAccess to base it on. If the SiteAccess root path (configured in `content.tree_root.location_id`) is different than the default, the prefix path that results from the configured content root is prepended to the final alias path. ### URL alias pattern configuration You can configure how Ibexa DXP generates URL aliases. The configuration is stored under the `ibexa.url_alias.slug_converter` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files), for example: ``` ibexa: url_alias: slug_converter: transformation: example_group separator: dash transformation_groups: example_group: commands: - space_normalize - hyphen_normalize - apostrophe_normalize - doublequote_normalize - your_custom_command cleanup_method: url_cleanup ``` | Option | Description | | ----------------------- | --------------------------------------------------------------------------------------------------------- | | `transformation` | Indicates which pattern is used by default. | | `separator` | Decides what separator is used. There are three types of separator available: dash, underscore and space. | | `transformation_groups` | Contains the available patterns for URL generation. | A transformation group consists of an array of commands (see [all available commands](https://github.com/ibexa/core/tree/main/src/lib/Resources/slug_converter/transformations)) and a [`cleanupText`](https://github.com/ibexa/core/blob/main/src/lib/Persistence/Legacy/Content/UrlAlias/SlugConverter.php#L286). You can make use of pre-defined transformation groups. You can also add your own, with your own set of commands. To add commands to an existing group, provide the group name and list the commands that you want to add. ### Regenerating URL aliases You can use the `ibexa:urls:regenerate-aliases` command to regenerate all URL aliases. After the command is applied, old aliases redirect to the new ones. Use it when: - you change URL alias configuration and want to regenerate old aliases - you encounter database corruption - you have content that doesn't have a URL alias > **Caution: Caution** > > Before you apply the command, back up your database and make sure it's not modified while the command is running. Execute the following command to regenerate aliases: ``` bin/console ibexa:urls:regenerate-aliases ``` You can also extend the command with the following parameters: - `--iteration-count` — Defines how many locations are processed at once to reduce memory usage - `--location-id` — Regenerates URL addresses for specific locations only, for example, `ibexa:urls:regenerate-aliases --location-id=1 --location-id=2` ## URL wildcards With wildcards, you can change the URL address for many content items at the same time, by replacing a portion of the destination's URL address. For example, you might want to shorten the path, or make the path meaningful. For each URL wildcard definition you set the wildcard pattern and its destination. Also, you can decide whether the user sees the content at the address that uses wildcards (Direct type), or is redirected to the original URL address of the destination (Forward type). For example, a URL wildcard called `pictures/*/*` can use `media/images/{1}/{2}` as destination. In this case, accessing `/pictures/home/photo/` loads `/media/images/home/photo/`. You can configure URL wildcards either in the back office, or with the public PHP API. Before you configure URL wildcards, you must enable the feature in configuration: ``` ibexa: url_wildcards: enabled: true ``` ### Configuring URL wildcards in the back office The **URL wildcards** tab contains all the information about each URL wildcard. You can delete or modify existing entries, or create new ones. > **Note: Note** > > To be able to modify wildcard support settings in the user interface, you must have the `content/urltranslator` policy. For more information about permissions, see [Permissions](https://doc.ibexa.co/en/latest/permissions/permissions/index.md). ### Configuring URL wildcards with the public PHP API You can create URL wildcards with the public PHP API by using the `URLWildcardService` service: ``` $source = 'pictures/*/*'; $destination = 'media/images/{1}/{2}'; $redirect = true; $urlWildcardService = $repository->getURLWildcardService(); $repository->sudo(function ($repository) use ($urlWildcardService, $source, $destination, $redirect) { $urlWildcardService->create($source, $destination, $redirect); }); ``` If `$redirect` is set to `true`, the redirection changes the URL address. If it's `false`, the old URL address is be used, with the new content. # URL API [`URLService`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-URLService.html) enables you to find, load and update external URLs used in RichText and URL fields. To view a list of all URLs, use [`URLService::findUrls`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-URLService.html#method_findUrls) `URLService::findUrls` takes as argument a [`URLQuery`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-URL-URLQuery.html), in which you need to specify: - query filter, for example, Section - Sort Clauses for URL queries - offset for search hits, used for paging the results - query limit. If value is `0`, search query doesn't return any search hits ``` // ... use Ibexa\Contracts\Core\Repository\URLService; use Ibexa\Contracts\Core\Repository\Values\URL\Query\Criterion; use Ibexa\Contracts\Core\Repository\Values\URL\Query\SortClause; use Ibexa\Contracts\Core\Repository\Values\URL\URLQuery; // ... $query = new URLQuery(); $query->filter = new Criterion\LogicalAnd( [ new Criterion\SectionIdentifier(['standard']), new Criterion\Validity(true), ] ); $query->sortClauses = [ new SortClause\URL(SortClause::SORT_DESC), ]; $query->offset = 0; $query->limit = 25; $results = $this->urlService->findUrls($query); ``` ## URL search reference For the reference of Search Criteria and Sort Clauses you can use in URL search, see [URL Search Criteria](https://doc.ibexa.co/en/latest/search/url_search_reference/url_search_criteria/index.md) and [URL Sort Clauses](https://doc.ibexa.co/en/latest/search/url_search_reference/url_search_sort_clauses/index.md). # User-generated content Ibexa DXP comes with content edition features via the Symfony stack. They're meant to allow the implementation of user-generated content from the front end, without entering the back office. ## Creating a new draft The `content/create/draft` route enables you to create a new draft for the selected content item. Pass the ID of the content item as an argument. For example, `content/create/draft/59` creates a new draft of the content item with ID 59. ## Creating a content item without using a draft The `/content/edit/nodraft` route shows a content item creation form for a given content type: | Argument | Type | Description | | ----------------------- | --------- | -------------------------------------------------------------------------- | | `contentTypeIdentifier` | `string` | The identifier of the content type to create. Example: `folder`, `article` | | `languageCode` | `string` | Language code the content item must be created in. Example: `eng-GB` | | `parentLocationId` | `integer` | ID of the location the content item must be created in. Example: `2` | This means that `/content/create/nodraft/folder/eng-GB/2` enables you to create a Folder in English as a child of location with ID 2. A limited subset of field types is supported: - `TextLine` - `TextBlock` - `Selection` - `Checkbox` - `User` - `Date` - `DateAndTime` - `Time` - `Integer` - `Float` - `URL` ## Editing a content item To edit an existing draft, use the `/content/edit/draft/` route, with the following arguments: | Argument | Type | Description | | -------------- | --------- | ------------------------------------------------------------------------ | | `contentId` | `integer` | ContentId of the item to edit. | | `versionNo` | `integer` | Number of the version to edit. The version must be an unpublished draft. | | `languageCode` | `string` | Language code of the version. Example: `eng-GB` | For example, `/content/edit/draft/1/5/eng-GB` enables you to edit draft 5 of content item 1 in English. ## Content editing templates You can use custom templates for the content editing forms. Define the templates under the `ibexa.system..content_edit.templates` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa: system: default: content_edit: templates: edit: content/edit/content_edit.html.twig create_draft: content/edit/content_create_draft.html.twig ``` # Browsing and viewing content To retrieve a content item and its information, you need to make use of the [`ContentService`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-ContentService.html). The service should be [injected into the constructor of your command or controller](https://doc.ibexa.co/en/latest/api/php_api/php_api/#service-container). > **Tip: Content REST API** > > To learn how to load content items using the REST API, see [REST API reference](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Objects/operation/api_contentobjects_contentId_get). > **Tip: Console commands** > > To learn more about commands in Symfony, refer to [Console Commands](https://symfony.com/doc/7.4/console.html). ## Viewing content metadata ### ContentInfo Basic content metadata is available through [`ContentInfo`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-ContentInfo.html) objects and their properties. This value object provides primitive fields, such as `contentTypeId`, `publishedDate`, or `mainLocationId`, and methods for retrieving selected properties. You can also use it to request other content-related value objects from various services: ``` contentService->loadContentInfo($contentId); $output->writeln("Name: $contentInfo->name"); $output->writeln('Last modified: ' . $contentInfo->modificationDate->format('Y-m-d')); $output->writeln('Published: ' . $contentInfo->publishedDate->format('Y-m-d')); $output->writeln("RemoteId: $contentInfo->remoteId"); $output->writeln("Main Language: $contentInfo->mainLanguageCode"); $output->writeln('Always available: ' . ($contentInfo->alwaysAvailable ? 'Yes' : 'No')); return self::SUCCESS; } } ``` `ContentInfo` is loaded from the [`ContentService`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-ContentService.html) (line 8). It provides you with basic content metadata such as modification and publication dates or main language code. > **Note: Retrieving content information in a controller** > > To retrieve content information in a controller, you also make use of the `ContentService`, but rendering specific elements (for example, content information or field values) is relegated to [templates](https://doc.ibexa.co/en/latest/templating/templates/templates/index.md). ### Locations To get the locations of a content item you need to make use of the [`LocationService`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-LocationService.html): ``` $output->writeln("RemoteId: $contentInfo->remoteId"); $output->writeln("Main Language: $contentInfo->mainLanguageCode"); $output->writeln('Always available: ' . ($contentInfo->alwaysAvailable ? 'Yes' : 'No')); // Locations $locations = $this->locationService->loadLocations($contentInfo); ``` [`LocationService::loadLocations`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-LocationService.html#method_loadLocations) uses `ContentInfo` to get all the locations of a content item. This method returns an array of [`Location`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Persistence-Content-Location.html) value objects. For each location, the code above prints out its `pathString` (the internal representation of the path). #### URL Aliases The [`URLAliasService`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-URLAliasService.html) additionally enables you to retrieve the human-readable [URL alias](https://doc.ibexa.co/en/latest/content_management/url_management/url_management/#url-aliases) of each location. [`URLAliasService::reverseLookup`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-URLAliasService.html#method_reverseLookup) gets the location's main [URL alias](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-URLAlias.html): ``` $versionInfos = $this->contentService->loadVersions($contentInfo); foreach ($versionInfos as $versionInfo) { $output->write("Version $versionInfo->versionNo"); $output->writeln(' in ' . $versionInfo->getInitialLanguage()->name); } ``` ### Content type You can retrieve the content type of a content item through the [`getContentType`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-ContentInfo.html#method_getContentType) method of the ContentInfo object: ``` $content = $this->contentService->loadContent($contentId); $output->writeln('Content type: ' . $content->getContentType()->getName()); ``` ### Versions To iterate over the versions of a content item, use the [`ContentService::loadVersions`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-ContentService.html#method_loadVersions) method, which returns an array of `VersionInfo` value objects. ``` $versionInfos = $this->contentService->loadVersions($contentInfo); foreach ($versionInfos as $versionInfo) { $output->write("Version $versionInfo->versionNo"); $output->write(' by ' . $versionInfo->getCreator()->getName()); $output->writeln(' in ' . $versionInfo->getInitialLanguage()->name); } ``` You can additionally provide the `loadVersions` method with the version status to get only versions of a specific status, for example: ``` $versionInfoArray = iterator_to_array($this->contentService->loadVersions($contentInfo, VersionInfo::STATUS_ARCHIVED)); ``` > **Note: Note** > > Requesting version data may be impossible for an anonymous user. Make sure to [authenticate](https://doc.ibexa.co/en/latest/api/php_api/php_api/#setting-the-repository-user) as a user with sufficient permissions. ### Relations Content Relations are versioned. To list Relations to and from your content, you need to pass a `VersionInfo` object to the [`ContentService::loadRelationList`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-ContentService.html#method_loadRelationList) method. This method loads only the specified subset of relations to improve performance and was created with pagination in mind. You can get the current version's `VersionInfo` using [`ContentService::loadVersionInfo`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-ContentService.html#method_loadVersionInfo). ``` $versionInfo = $this->contentService->loadVersionInfo($contentInfo); $relationCount = $this->contentService->countRelations($versionInfo); $relationList = $this->contentService->loadRelationList($versionInfo, 0, $relationCount); foreach ($relationList as $relationListItem) { $name = $relationListItem->hasRelation() ? $relationListItem->getRelation()->destinationContentInfo->name : '(Unauthorized)'; $output->writeln("Relation to content '$name'"); } ``` You can also specify the version number as the second argument to get Relations for a specific version: ``` $versionInfo = $this->contentService->loadVersionInfo($contentInfo, 2); ``` `loadRelationList` provides an iterable [`RelationList`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-RelationList.html) object listing [`Relation`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Relation.html) objects. `Relation` has two main properties: `destinationContentInfo`, and `sourceContentInfo`. It also holds the [relation type](https://doc.ibexa.co/en/latest/content_management/content_relations/index.md), and the optional field this relation is made with. ### Owning user You can use the `getOwner` method of the `ContentInfo` object to load the content item's owner as a `User` value object. ``` $output->writeln('Owner: ' . $contentInfo->getOwner()->getName()); ``` To get the creator of the current version and not the content item's owner, you need to use the `creatorId` property from the current version's `VersionInfo` object. ### Section You can find the section to which a content item belongs through the [`getSection`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-ContentInfo.html#method_getSection) method of the ContentInfo object: ``` $output->writeln('Section: ' . $contentInfo->getSection()->name); ``` > **Note: Note** > > Requesting section data may be impossible for an anonymous user. Make sure to [authenticate](https://doc.ibexa.co/en/latest/api/php_api/php_api/#setting-the-repository-user) as a user with sufficient permissions. ### Object states You can retrieve [object states](https://doc.ibexa.co/en/latest/administration/content_organization/object_states/index.md) of a content item using [`ObjectStateService::getContentState`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-ObjectStateService.html#method_getContentState). You need to provide it with the object state group. All object state groups can be retrieved through [`loadObjectStateGroups`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-ObjectStateService.html#method_loadObjectStateGroups). ``` $stateGroups = $this->objectStateService->loadObjectStateGroups(); foreach ($stateGroups as $stateGroup) { $state = $this->objectStateService->getContentState($contentInfo, $stateGroup); $output->writeln("Object state: $state->identifier"); } ``` ## Viewing content with fields To retrieve the fields of the selected content item, you can use the following command: ``` getArgument('contentId'); $content = $this->contentService->loadContent($contentId); $contentType = $this->contentTypeService->loadContentType($content->contentInfo->contentTypeId); foreach ($contentType->fieldDefinitions as $fieldDefinition) { $output->writeln('Field: ' . $fieldDefinition->identifier); $fieldType = $this->fieldTypeService->getFieldType($fieldDefinition->fieldTypeIdentifier); $field = $content->getFieldValue($fieldDefinition->identifier); $valueHash = $fieldType->toHash($field); $output->writeln('Value:'); $output->writeln($valueHash); } return self::SUCCESS; } } ``` Line 9 shows how [`ContentService::loadContent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-ContentService.html#method_loadContent) loads the content item provided to the command. Line 10 makes use of the [`ContentTypeService`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-ContentTypeService.html) to retrieve the content type of the requested item. Lines 12-19 iterate over fields defined by the content type. For each field they print out its identifier, and then using [`FieldTypeService`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-FieldTypeService.html) retrieve the field's value and print it out to the console. ## Viewing content in different languages The repository is SiteAccess-aware, so languages defined by the SiteAccess are automatically taken into account when loading content. To load a specific language, provide its language code when loading the content item: ``` $content = $this->contentService->loadContent($contentId, ['ger-DE']); ``` To load all languages as a prioritized list, use `Language::ALL`: ``` $contentService->loadContent($content->id, Language::ALL); ``` ## Getting all content in a subtree To go through all the content items contained in a subtree, you need to use the [`LocationService`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-LocationService.html). ``` private function browseLocation(Location $location, OutputInterface $output, int $depth = 0): void { $output->writeln($location->contentInfo->name); $children = $this->locationService->loadLocationChildren($location); foreach ($children->locations as $child) { $this->browseLocation($child, $output, $depth + 1); } } protected function execute(InputInterface $input, OutputInterface $output): int { $locationId = (int) $input->getArgument('locationId'); $location = $this->locationService->loadLocation($locationId); $this->browseLocation($location, $output); return self::SUCCESS; } ``` `loadLocation` (line 15) returns a value object, here a `Location`. [`LocationService::loadLocationChildren`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-LocationService.html#method_loadLocationChildren) (line 5) returns a [`LocationList`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-LocationList.html) value object that you can iterate over. > **Note: Note** > > Refer to [Searching](https://doc.ibexa.co/en/latest/search/search_api/index.md) for information on more complex search queries. ## Getting parent location To get the parent location of content, you first need to determine which location is the main one, in case the content item has multiple locations. You can do it through the `getMainLocation` method of the ContentInfo object. Next, use the `getParentLocation` method of the location object to access the parent location: ``` $mainLocation = $contentInfo->getMainLocation(); $output->writeln("Parent Location: " . $mainLocation->getParentLocation()->pathString); ``` ## Getting content from a location When dealing with location objects (and Trash objects), you can get access to content item directly using `$location->getContent`. In Twig this can also be accessed by `location.content`. This is a lazy property. It triggers loading of content when first used. In case of bulk of locations coming from Search or location Service, the content is also loaded in bulk for the whole location result set. ## Comparing content versions You can compare two versions of a content item using the `VersionComparisonService`. The versions must have the same language. For example, to get the comparison between the `name` field of two versions: ``` $versionFrom = $this->contentService->loadVersionInfo($contentInfo, $versionFromId); $versionTo = $this->contentService->loadVersionInfo($contentInfo, $versionToId); $nameComparison = $this->comparisonService->compare($versionFrom, $versionTo)->getFieldValueDiffByIdentifier('name')->getComparisonResult(); ``` `getComparisonResult` returns a `ComparisonResult` object, which depends on the field type being compared. In the example of a Text Line (ibexa_string) field, it's an array of `StringDiff` objects. Each diff contains a section of the field to compare (for example, a part of a text line) and its status, which can be "unchanged", "added" or "removed". # Creating content > **Note: Note** > > Creating most objects is impossible for an anonymous user. Make sure to [authenticate](https://doc.ibexa.co/en/latest/api/php_api/php_api/#setting-the-repository-user) as a user with sufficient permissions. > **Tip: Content REST API** > > To learn how to create content items using the REST API, see [REST API reference](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Objects/operation/api_contentobjects_post). ## Creating content item draft Value objects such as content items are read-only, so to create or modify them you need to use structs. [`ContentService::newContentCreateStruct`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-ContentService.html#method_newContentCreateStruct) returns a new [`ContentCreateStruct`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-ContentCreateStruct.html) object. ``` $contentType = $this->contentTypeService->loadContentTypeByIdentifier($contentTypeIdentifier); $contentCreateStruct = $this->contentService->newContentCreateStruct($contentType, 'eng-GB'); $contentCreateStruct->setField('name', $name); $locationCreateStruct = $this->locationService->newLocationCreateStruct($parentLocationId); $draft = $this->contentService->createContent($contentCreateStruct, [$locationCreateStruct]); $output->writeln('Created a draft of ' . $contentType->getName() . ' with name ' . $draft->getName()); ``` This command creates a draft using [`ContentService::createContent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-ContentService.html#method_createContent) (line 6). This method must receive a `ContentCreateStruct` and an array of location structs. `ContentCreateStruct` (which extends `ContentStruct`) is created through [`ContentService::newContentCreateStruct`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-ContentService.html#method_newContentCreateStruct) (line 1), which receives the content type and the primary language for the content item. For information about translating a content item into other languages, see [Translating content](#translating-content). [`ContentStruct::setField`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-ContentStruct.html#method_setField) (line 2) enables you to define the field values. When the field accepts a simple value, you can provide it directly, as in the example above. For some field types, for example [images](#creating-an-image), you need to provide an instance of a Value type. ### Creating an image Image field type requires an instance of its Value type, which you must provide to the [`ContentStruct::setField`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-ContentStruct.html#method_setField) method. Therefore, when creating a content item of the Image type (or any other content type with an `image` field type), the `ContentCreateStruct` is slightly more complex than in the previous example: ``` $contentType = $this->contentTypeService->loadContentTypeByIdentifier('image'); $contentCreateStruct = $this->contentService->newContentCreateStruct($contentType, 'eng-GB'); $contentCreateStruct->setField('name', $name); $imageValue = new Value( [ 'path' => $file, 'fileSize' => filesize($file), 'fileName' => basename((string) $file), 'alternativeText' => $name, ] ); $contentCreateStruct->setField('image', $imageValue); ``` Value of the Image field type contains the path to the image file and other basic information based on the input file. ### Creating content with RichText The RichText field accepts values in a custom flavor of [Docbook](https://github.com/docbook/wiki/wiki) format. For example, to add a RichText paragraph, provide the following as input: ```
    Description of your content item.
    ``` To learn more about the format and how it represents different elements of rich text, see [RichText field type reference](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/richtextfield/#custom-docbook-format). ## Publishing a draft [`ContentService::createContent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-ContentService.html#method_createContent) creates a content item with only one draft version. To publish it, use [`ContentService::publishVersion`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-ContentService.html#method_publishVersion). This method must get the [`VersionInfo`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-VersionInfo.html) object of a draft version. ``` $content = $this->contentService->publishVersion($draft->versionInfo); ``` ## Updating content To update an existing content item, you need to prepare a [`ContentUpdateStruct`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-ContentUpdateStruct.html) and pass it to [`ContentService::updateContent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-ContentService.html#method_updateContent). This method works on a draft, so to publish your changes you need to use [`ContentService::publishVersion`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-ContentService.html#method_publishVersion) as well: ``` $contentInfo = $this->contentService->loadContentInfo($contentId); $contentDraft = $this->contentService->createContentDraft($contentInfo); $contentUpdateStruct = $this->contentService->newContentUpdateStruct(); $contentUpdateStruct->initialLanguageCode = 'eng-GB'; $contentUpdateStruct->setField('name', $newName); $contentDraft = $this->contentService->updateContent($contentDraft->versionInfo, $contentUpdateStruct); $this->contentService->publishVersion($contentDraft->versionInfo); ``` ## Translating content Content [translations](https://doc.ibexa.co/en/latest/multisite/languages/languages/#language-versions) are created per version. By default every version contains all existing translations. To translate a content item to a new language, you need to update it and provide a new `initialLanguageCode`: ``` $contentInfo = $this->contentService->loadContentInfo($contentId); $contentDraft = $this->contentService->createContentDraft($contentInfo); $contentUpdateStruct = $this->contentService->newContentUpdateStruct(); $contentUpdateStruct->initialLanguageCode = $language; $contentUpdateStruct->setField('name', $newName); $contentDraft = $this->contentService->updateContent($contentDraft->versionInfo, $contentUpdateStruct); $this->contentService->publishVersion($contentDraft->versionInfo); ``` You can also update content in multiple languages at once using the `setField` method's third argument. Only one language can still be set as a version's initial language: ``` $contentUpdateStruct->setField('name', $nameInSecondaryLanguage, $secondaryLanguage); ``` ### Deleting a translation You can delete a single translation from a content item's version using [`ContentService::deleteTranslationFromDraft`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-ContentService.html#method_deleteTranslationFromDraft). The method must be provided with a `VersionInfo` object and the code of the language to delete: ``` $this->contentService->deleteTranslationFromDraft($versionInfo, $language); ``` # Managing content ## Locations You can manage [locations](https://doc.ibexa.co/en/latest/content_management/locations/index.md) that hold content using [`LocationService`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-LocationService.html). > **Tip: Location REST API** > > To learn how to manage locations using the REST API, see [REST API reference](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Objects/operation/api_contentobjects_contentIdlocations_post). ### Adding a new location to a content item Every published content item must have at least one location. One content item can have more that one location, which means it's presented in more than one place in the content tree. Creating a new location, like creating content, requires using a struct, because a location value object is read-only. To add a new location to existing content you need to create a [`LocationCreateStruct`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-LocationCreateStruct.html) and pass it to the [`LocationService::createLocation`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-LocationService.html#method_createLocation) method: ``` $locationCreateStruct = $this->locationService->newLocationCreateStruct($parentLocationId); $contentInfo = $this->contentService->loadContentInfo($contentId); $newLocation = $this->locationService->createLocation($contentInfo, $locationCreateStruct); ``` `LocationCreateStruct` must receive the parent location ID. It sets the `parentLocationId` property of the new location. You can also provide other properties for the location, otherwise they're set to their defaults: ``` $locationCreateStruct->priority = 500; $locationCreateStruct->hidden = true; ``` ### Changing the main location When a content item has more that one location, one location is always considered the main one. You can change the main location using [`ContentService`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-ContentService.html), by updating the `ContentInfo` with a [`ContentUpdateStruct`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-ContentUpdateStruct.html) that sets the new main location: ``` $contentUpdateStruct = $this->contentService->newContentMetadataUpdateStruct(); $contentUpdateStruct->mainLocationId = $locationId; $this->contentService->updateContentMetadata($contentInfo, $contentUpdateStruct); ``` ### Hiding and revealing locations To hide or reveal (unhide) a location you need to make use of [`LocationService::hideLocation`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-LocationService.html#method_hideLocation) or [`LocationService::unhideLocation`:](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-LocationService.html#method_unhideLocation) ``` $this->locationService->hideLocation($location); $this->locationService->unhideLocation($location); ``` See [location visibility](https://doc.ibexa.co/en/latest/content_management/locations/#location-visibility) for detailed information on the behavior of visible and hidden Locations. ### Deleting a location You can remove a location either by deleting it, or sending it to Trash. Deleting makes use of [`LocationService::deleteLocation()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-LocationService.html#method_deleteLocation). It permanently deletes the location, together with its whole subtree. Content which has only this one location is permanently deleted as well. Content which has more locations is still available in its other locations. If you delete the [main location](#changing-the-main-location) of a content item that has more locations, another location becomes the main one. ``` $location = $this->locationService->loadLocation($locationId); $this->locationService->deleteLocation($location); ``` To send the location and its subtree to Trash, use [`TrashService::trash`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-TrashService.html#method_trash). Items in Trash can be later [restored, or deleted permanently](#trash). ``` $this->trashService->trash($location); ``` ### Moving and copying a subtree You can move a location with its whole subtree using [`LocationService::moveSubtree`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-LocationService.html#method_moveSubtree): ``` $sourceLocation = $this->locationService->loadLocation($locationId); $targetLocation = $this->locationService->loadLocation($targetLocationId); $this->locationService->moveSubtree($sourceLocation, $targetLocation); ``` [`LocationService::copySubtree`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-LocationService.html#method_copySubtree) is used in the same way, but it copies the location and its subtree instead of moving it. > **Tip: Tip** > > To copy a subtree you can also make use of the built-in `copy-subtree` command: `bin/console ibexa:copy-subtree `. > **Note: Note** > > [Copy subtree limit](https://doc.ibexa.co/en/latest/administration/back_office/back_office_configuration/#copy-subtree-limit) only applies to operations in the back office. It's ignored when copying subtrees using the PHP API. ## Trash > **Tip: Trash REST API** > > To learn how to manage Trash using the REST API, see [REST API reference](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Trash). To empty the Trash (remove all locations in Trash), use [`TrashService::emptyTrash`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-TrashService.html#method_emptyTrash), which takes no arguments. You can recover an item from Trash using [`TrashService::recover`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-TrashService.html#method_recover). You must provide the method with the ID of the object in Trash. Trash location is identical to the origin location of the object. ``` $this->trashService->recover($trashItem, $newParent); ``` The content item is restored under its previous location. You can also provide a different location to restore in as a second argument: ``` $newParent = $this->locationService->loadLocation($location); $this->trashService->recover($trashItem, $newParent); ``` You can also search through Trash items and sort the results using several public PHP API Search Criteria and Sort Clauses that have been exposed for `TrashService` queries. For more information, see [Search in trash](https://doc.ibexa.co/en/latest/search/search_api/#search-in-trash). ## Content types > **Tip: Content type REST API** > > To learn how to manage content types using the REST API, see REST API reference for [content types](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Type) and [content type groups](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Type-Groups). ### Adding content types To operate on content types, you need to make use of [`ContentTypeService`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-ContentTypeService.html). Adding a new content type, like creating content, must happen with the use of a struct, because a content type value object is read-only. In this case you use [`ContentTypeCreateStruct`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-ContentType-ContentTypeCreateStruct.html). A content type must have at least one name, in the main language, and at least one field definition. ``` $contentTypeCreateStruct = $this->contentTypeService->newContentTypeCreateStruct($contentTypeIdentifier); $contentTypeCreateStruct->mainLanguageCode = 'eng-GB'; $contentTypeCreateStruct->nameSchema = ''; $contentTypeCreateStruct->names = [ 'eng-GB' => $contentTypeIdentifier, ]; $titleFieldCreateStruct = $this->contentTypeService->newFieldDefinitionCreateStruct('name', 'ibexa_string'); $contentTypeCreateStruct->addFieldDefinition($titleFieldCreateStruct); $contentTypeDraft = $this->contentTypeService->createContentType( $contentTypeCreateStruct, [$contentTypeGroup] ); $this->contentTypeService->publishContentTypeDraft($contentTypeDraft); ``` You can specify more details of the field definition in the create struct, for example: ``` $titleFieldCreateStruct = $this->contentTypeService->newFieldDefinitionCreateStruct('name', 'ibexa_string'); $titleFieldCreateStruct->names = ['eng-GB' => 'Name']; $titleFieldCreateStruct->descriptions = ['eng-GB' => 'The name']; $titleFieldCreateStruct->fieldGroup = 'content'; $titleFieldCreateStruct->position = 10; $titleFieldCreateStruct->isTranslatable = true; $titleFieldCreateStruct->isRequired = true; $titleFieldCreateStruct->isSearchable = true; ``` ### Copying content types To copy a content type, use [`ContentTypeService::copyContentType`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-ContentTypeService.html#method_copyContentType): ``` $contentTypeToCopy = $this->contentTypeService->loadContentTypeByIdentifier($contentTypeIdentifier); $copy = $this->contentTypeService->copyContentType($contentTypeToCopy); ``` The copy is automatically getting an identifier based on the original content type identifier and the copy's ID, for example: `copy_of_folder_21`. To change the identifier of the copy, use a [`ContentTypeUpdateStruct`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-ContentType-ContentTypeUpdateStruct.html): ``` $copy = $this->contentTypeService->copyContentType($contentTypeToCopy); $copyDraft = $this->contentTypeService->createContentTypeDraft($copy); $copyUpdateStruct = $this->contentTypeService->newContentTypeUpdateStruct(); $copyUpdateStruct->identifier = $copyIdentifier; $copyUpdateStruct->names = ['eng-GB' => $copyIdentifier]; $this->contentTypeService->updateContentTypeDraft($copyDraft, $copyUpdateStruct); ``` ### Finding and filtering content types You can find content types that match specific criteria by using the [`ContentTypeService::findContentTypes()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-ContentTypeService.html#method_findContentTypes) method. This method accepts a `ContentTypeQuery` object that supports filtering and sorting by IDs, identifiers, group membership, and other criteria. > **Note: Criteria, sort clauses and REST APIs** > > For a full list of available criteria and sort clauses that you can use when finding and filtering content types, see [Content Type Search Criteria](https://doc.ibexa.co/en/latest/search/content_type_search_reference/content_type_criteria/index.md) and [Content Type Search Sort Clauses](https://doc.ibexa.co/en/latest/search/content_type_search_reference/content_type_sort_clauses/index.md) references. > > For the REST API, see [Filter content types](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Type/operation/api_contenttypesview_post). The following example shows how you can use the criteria to find content types: ``` contentTypeService->findContentTypes($query); $output->writeln('Found ' . $searchResult->getTotalCount() . ' content type(s):'); foreach ($searchResult->getContentTypes() as $contentType) { $output->writeln(sprintf( '- [%d] %s (identifier: %s)', $contentType->id, $contentType->getName(), $contentType->identifier )); } return Command::SUCCESS; } } ``` #### Query parameters When constructing a `ContentTypeQuery`, you can pass the following parameters: - `?CriterionInterface $criterion = null` — a filter to apply (use one or a combination of the criteria above) - `array $sortClauses = []` — list of sort clauses to order the results - `int $offset = 0` — starting offset (for pagination) - `int $limit = 25` — maximum number of results to return ## Calendar events You can handle the calendar using `CalendarServiceInterface` (`Ibexa\Contracts\Calendar\CalendarServiceInterface`). > **Tip: Calendar REST API** > > To learn how to manage the Calendar using the REST API, see [REST API reference](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Calendar). ### Getting events To get a list of events for a specified time period, use the `CalendarServiceInterface::getEvents` method. You need to provide the method with an EventQuery, which takes a date range and a count as the minimum of parameters: ``` $dateFrom = new \DateTimeImmutable('2023-01-01T10:00:00+00:00'); $dateTo = new \DateTimeImmutable('2023-12-31T10:0:00+00:00'); $dateRange = new Calendar\DateRange($dateFrom, $dateTo); $eventQuery = new Calendar\EventQuery($dateRange, 10); $eventList = $this->calendarService->getEvents($eventQuery); foreach ($eventList as $event) { $output->writeln($event->getName() . '; date: ' . $event->getDateTime()->format('T Y-m-d H:i:s')); } ``` You can also get the first and last event in the list by using the `first()` and `last()` methods of an `EventCollection` (`Ibexa\Contracts\Calendar\EventCollection`): ``` $eventCollection = $eventList->getEvents(); $output->writeln('First event: ' . $eventCollection->first()->getName() . '; date: ' . $eventCollection->first()->getDateTime()->format('T Y-m-d H:i:s')); ``` You can process the events in a collection using the `find(Closure $predicate)`, `filter(Closure $predicate)`, `map(Closure $callback)` or `slice(int $offset, ?int $length = null)` methods of `EventCollection`, for example: ``` $newCollection = $eventCollection->slice(3, 5); foreach ($newCollection as $event) { $output->writeln('New collection: ' . $event->getName() . '; date: ' . $event->getDateTime()->format('T Y-m-d H:i:s')); } ``` ### Performing calendar actions You can perform a calendar action (for example, reschedule or unschedule calendar events) using the `CalendarServiceInterface::executeAction()` method. You must pass an `Ibexa\Contracts\Calendar\EventAction\EventActionContext` instance as argument. `EventActionContext` defines events on which the action is performed, and action-specific parameters, for example, a new date: ``` $newDate = new \DateTimeImmutable('2023-12-06T13:00:00+00:00'); $context = new RescheduleEventActionContext($eventCollection, $newDate); $this->calendarService->executeAction($context); ``` # Bookmark API [`BookmarkService`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-BookmarkService.html) enables you to read, add and remove bookmarks from content. > **Tip: Bookmark REST API** > > To learn how to manage bookmarks using the REST API, see [REST API reference](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Bookmark). To view a list of all bookmarks, use [`BookmarkService::loadBookmarks`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-BookmarkService.html#method_loadBookmarks): ``` $bookmarkList = $this->bookmarkService->loadBookmarks(); $output->writeln('Total bookmarks: ' . $bookmarkList->totalCount); foreach ($bookmarkList->items as $bookmark) { $output->writeln($bookmark->getContentInfo()->name); } ``` You can add a bookmark to a content item by providing its Location object to the [`BookmarkService::createBookmark`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-BookmarkService.html#method_createBookmark) method: ``` $location = $this->locationService->loadLocation($locationId); $this->bookmarkService->createBookmark($location); ``` You can remove a bookmark from a location with [`BookmarkService::deleteBookmark`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-BookmarkService.html#method_deleteBookmark): ``` $this->bookmarkService->deleteBookmark($location); ``` # Section API [Sections](https://doc.ibexa.co/en/latest/administration/content_organization/sections/index.md) enable you to divide content into groups which can later be used, for example, as basis for permissions. You can manage sections by using the PHP API by using `SectionService`. > **Tip: Section REST API** > > To learn how to manage sections using the REST API, see [REST API reference](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Section). ## Creating sections To create a new section, you need to make use of the [`SectionCreateStruct`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-SectionCreateStruct.html) and pass it to the [`SectionService::createSection`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-SectionService.html#method_createSection) method: ``` $sectionCreateStruct = $this->sectionService->newSectionCreateStruct(); $sectionCreateStruct->name = $sectionName; $sectionCreateStruct->identifier = $sectionIdentifier; $this->sectionService->createSection($sectionCreateStruct); ``` ## Getting section information You can use `SectionService` to retrieve section information such as whether it's in use: ``` $output->writeln(( $this->sectionService->isSectionUsed($section) ? 'This section is in use.' : 'This section is not in use.' )); ``` ## Listing content in a section To list content items assigned to a section you need to make a [query](https://doc.ibexa.co/en/latest/search/search_api/index.md) for content belonging to this section, by applying the [`SearchService`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-SearchService.html). You can also use the query to get the total number of assigned content items: ``` $query = new LocationQuery(); $query->filter = new Criterion\SectionId([ $section->id, ]); $result = $this->searchService->findContentInfo($query); foreach ($result->searchHits as $searchResult) { $output->writeln('* ' . $searchResult->valueObject->name); } ``` ## Assigning section to content To assign content to a section, use the [`SectionService::assignSection`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-SectionService.html#method_assignSection) method. You need to provide it with the `ContentInfo` object of the content item, and the [`Section`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Section.html) object: ``` $section = $this->sectionService->loadSectionByIdentifier($sectionIdentifier); $contentInfo = $this->contentService->loadContentInfo($contentId); $this->sectionService->assignSection($contentInfo, $section); ``` Assigning a section to content doesn't automatically assign it to the content item's children. # Object state API [Object states](https://doc.ibexa.co/en/latest/administration/content_organization/object_states/index.md) enable you to set a custom state to any content. States are grouped into object state groups. You can manage Object states by using the PHP API by using `ObjectStateService`. > **Tip: Object state REST API** > > To learn how to manage object states using the REST API, see [REST API reference](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Objects/operation/api_contentobjects_contentIdobjectstates_get). ## Getting object state information You can use the [`ObjectStateService`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-ObjectStateService.html) to get information about object state groups or object states. ``` $objectStateGroup = $this->objectStateService->loadObjectStateGroupByIdentifier('ibexa_lock'); $objectState = $this->objectStateService->loadObjectStateByIdentifier($objectStateGroup, 'locked'); $output->writeln($objectStateGroup->getName()); $output->writeln($objectState->getName()); ``` ## Creating object states To create an object state group and add object states to it, you need to make use of the [`ObjectStateService`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-ObjectStateService.html): ``` $objectStateGroupStruct = $this->objectStateService->newObjectStateGroupCreateStruct($objectStateGroupIdentifier); $objectStateGroupStruct->defaultLanguageCode = 'eng-GB'; $objectStateGroupStruct->names = ['eng-GB' => $objectStateGroupIdentifier]; $newObjectStateGroup = $this->objectStateService->createObjectStateGroup($objectStateGroupStruct); ``` [`ObjectStateService::createObjectStateGroup`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-ObjectStateService.html#method_createObjectStateGroup) takes as argument an [`ObjectStateGroupCreateStruct`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-ObjectState-ObjectStateGroupCreateStruct.html), in which you need to specify the identifier, default language and at least one name for the group. To create an object state inside a group, use [`ObjectStateService::newObjectStateCreateStruct`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-ObjectStateService.html#method_newObjectStateCreateStruct) and provide it with an `ObjectStateCreateStruct`: ``` $stateStruct = $this->objectStateService->newObjectStateCreateStruct($objectStateIdentifier); $stateStruct->defaultLanguageCode = 'eng-GB'; $stateStruct->names = ['eng-GB' => $objectStateIdentifier]; $this->objectStateService->createObjectState($newObjectStateGroup, $stateStruct); ``` ## Assigning object state To assign an object state to a content item, use [`ObjectStateService::setContentState`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-ObjectStateService.html#method_setContentState). Provide it with a `ContentInfo` object of the content item, the object state group and the object state: ``` $contentInfo = $this->contentService->loadContentInfo($contentId); $objectStateGroup = $this->objectStateService->loadObjectStateGroupByIdentifier($objectStateGroupIdentifier); $objectState = $this->objectStateService->loadObjectStateByIdentifier($objectStateGroup, $objectStateToAssign); $this->objectStateService->setContentState($contentInfo, $objectStateGroup, $objectState); ``` # Data migration Data migration allows exporting and importing selected data from an Ibexa DXP installation. [*Exporting*](https://doc.ibexa.co/en/latest/content_management/data_migration/exporting_data/index.md) data consists in saving selected repository information in YAML format. [*Importing*](https://doc.ibexa.co/en/latest/content_management/data_migration/importing_data/index.md) reads migration YAML files and creates or modifies repository content based on them. Between installation, you can migrate your repository data, for example, content items, content types, languages, object states, or sections. You can use migrations in projects that require the same data to be present across multiple instances. You can use them for project templates. Migrations are able to store shared data, so they can be applied for each new project you start, or incrementally upgrade older projects to your new standard, if needed. They're a developer-friendly tool that allows you to share data without writing code. You can run data migrations either with a command, or with the [PHP API](https://doc.ibexa.co/en/latest/content_management/data_migration/data_migration_api/index.md). - [Importing data](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/content_management/data_migration/importing_data/): Import data into your repository from prepared YAML files. - [Exporting data](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/content_management/data_migration/exporting_data/): Export repository data to use in future data migrations. - [Data migration actions](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/content_management/data_migration/data_migration_actions/): Data migration actions enable you to run special operations while executing data migrations, such as assigning roles, sections, Objects states, and more. - [Managing migrations](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/content_management/data_migration/managing_migrations/): Manage data migrations by adding files, converting from Kaliop migration bundle, checking migration status, and setting up configuration. # Importing data To import data from YAML migration files into repository, you run the `ibexa:migrations:migrate` command. The `ibexa:migrations:import` command automatically places migration files in the correct folder. Alternatively, you can place the files manually in the `src/Migrations/Ibexa/migrations` folder or in [a custom folder that you configure](https://doc.ibexa.co/en/latest/content_management/data_migration/managing_migrations/#migration-folders), and specify the file name within this folder as parameter. If you don't specify the file, all files within this directory are used. ``` php bin/console ibexa:migrations:migrate --file=my_data_export.yaml --siteaccess=admin ``` Migrations store execution metadata in the `ibexa_migrations` database table. This allows incremental upgrades: the `ibexa:migrations:migrate` command ignores files that it had previously executed. The [`--siteaccess` option](https://doc.ibexa.co/en/latest/content_management/data_migration/exporting_data/#siteaccess) usage can be relevant when multiple languages or multiple repositories are used. ## Migration step A data migration step is a single operation in data migration process that combines a mode (for example: `create`, `update`, `delete`) and a type (for example: `content`, `section`, `currency`), with optional additional information depending on the specific step. In a migration file, a step is an array item starting with the mandatory properties `type` and `mode`, for example: ``` - type: content mode: create ``` Then, the step is described by additional properties depending on its type and mode. - See [Available migrations](#available-migrations) for the modes available for each type. - See [Migration examples](#migration-examples) to explore what you can do with each type. - For a custom migration step, see [Create data migration step](https://doc.ibexa.co/en/latest/content_management/data_migration/create_data_migration_step/index.md). ## Available migrations The following data migration step modes are available: | `type` | `create` | `update` | `delete` | `swap` | `trash` | | ---------------------- | -------- | -------- | -------- | ------ | ------- | | `action_configuration` | Yes | Yes | Yes | | | | `attribute` | Yes | Yes | Yes | | | | `attribute_group` | Yes | Yes | Yes | | | | `content_type` | Yes | Yes | Yes | | | | `content_type_group` | Yes | Yes | Yes | | | | `content` | Yes | Yes | Yes | | | | `currency` | Yes | Yes | Yes | | | | `customer_group` | Yes | Yes | Yes | | | | `discount` | Yes | Yes | | | | | `discount_code` | Yes | | | | | | `language` | Yes | | | | | | `location` | | Yes | | Yes | Yes | | `object_state` | Yes | | | | | | `object_state_group` | Yes | | | | | | `payment_method` | Yes | | | | | | `product_asset` | Yes | | | | | | `product_availability` | Yes | | | | | | `product_price` | Yes | | | | | | `product_variant` | Yes | | | | | | `role` | Yes | Yes | Yes | | | | `section` | Yes | Yes | | | | | `segment` | Yes | Yes | Yes | | | | `segment_group` | Yes | Yes | Yes | | | | `setting` | Yes | Yes | Yes | | | | `user` | Yes | Yes | | | | | `user_group` | Yes | Yes | Yes | | | Additionally, the following special migration types are available: | `type` | `execute` | | ------------ | --------- | | `repeatable` | Yes | | `sql` | Yes | | `try_catch` | Yes | ### Repeatable steps You can run a set of one or more similar migration steps multiple times by using the special `repeatable` migration type. A repeatable migration performs the defined migration steps as many times as the `iterations` setting declares. ``` - type: repeatable mode: create iterations: 5 steps: ``` > **Tip: Tip** > > You can use repeatable migration steps, for example, to quickly generate large numbers of content items for testing purposes. You can vary the operations using the iteration counter. For example, to create five Folders, with names ranging from "Folder 0" to "Folder 4", you can run the following migration using the iteration counter `i`: ``` - type: repeatable mode: create iterations: 5 steps: - type: content mode: create metadata: contentType: folder mainTranslation: eng-GB location: parentLocationId: 2 fields: - fieldDefIdentifier: name languageCode: eng-GB value: 'Folder ###SSS i SSS###' ``` To vary the content name, the migration above uses [Symfony expression syntax](#expression-syntax). In the example above, the expression is enclosed in `###` and the repeated string `SSS`. > **Note: Note** > > Iteration counter is assigned to `i` by default, but you can modify it in the `iteration_counter_name` setting. #### Generating fake data You can also generate fake data with the help of [`FakerPHP`](https://fakerphp.org/). To use it, first install Faker on your system: ``` composer require fakerphp/faker ``` Then, you can use `faker()` in expressions, for example: ``` - fieldDefIdentifier: short_name languageCode: eng-GB value: '### faker().name() ###' ``` This step generates field values with fake personal names. ### SQL migrations You can execute raw SQL queries directly in migrations by using the `sql` migration type. Use it for custom database operations that don't fit into standard entity migrations, such as creating custom tables or performing bulk updates. Each query requires a `driver` property that specifies which database system the query is for. The migration system automatically filters queries and executes only those matching your current database driver. ``` - type: sql mode: execute query: - driver: mysql sql: 'INSERT INTO test_table (test_value) VALUES ("foo");' - driver: sqlite sql: 'INSERT INTO test_table (test_value) VALUES ("foo");' - driver: postgresql sql: "INSERT INTO test_table (test_value) VALUES ('foo');" ``` The supported database drivers are: - `mysql` - MySQL/MariaDB - `postgresql` - PostgreSQL - `sqlite` - SQLite You can define queries for multiple database drivers in a single migration step. The system executes only the queries that match your configured database platform. If no matching queries are found, the migration throws an error. > **Caution: Caution** > > SQL migrations bypass the content model abstraction layer and directly modify the database. Use them with caution and ensure your queries are compatible with your target database system. ### Error handling with try-catch You can wrap one or more migration steps with a `try_catch` step to handle exceptions gracefully. Use it for migration steps that may fail under specific conditions but should not halt the entire migration process. For example, you can ensure a language creation migration step succeeds even if the language already exists. If the migration step fails for this reason, the exception is suppressed, allowing the remaining migrations to proceed without interruption. A `try_catch` migration requires the `steps` property and accepts optional `allowed_exceptions` and `stop_after_first_exception` settings. Default values are: - `allowed_exceptions`: empty list - `stop_after_first_exception`: `true` ``` - type: try_catch mode: execute allowed_exceptions: - Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException stop_after_first_exception: true steps: - type: language mode: create metadata: languageCode: ger-DE name: German enabled: true ``` When an exception is thrown within a `try_catch` step, it's compared against the list of `allowed_exceptions`. If the exception matches, it's caught and the migration step continues or stops depending on the `stop_after_first_exception` configuration setting. The migration step is marked as successful and the migration process continues. Non-matching exceptions throw immediately, halting the migration process and returning an error. ### Expression syntax You can use [Symfony expression syntax](https://symfony.com/doc/7.4/reference/formats/expression_language.html) in data migrations, like in [repeatable steps](#repeatable-steps), where you can use it to generate varied content in migration steps. The expression syntax uses the following structure: `### ###` The `IDENTIFIER` can be any repeated string that encloses the actual expression. #### Built-in functions Built-in expression language functions that are tagged with `ibexa.migrations.template.expression_language.function`: - `to_bool`, `to_int`, `to_float`, `to_string` - convert various data types by passing them into PHP casting functions (like `floatval`, `intval`, and others). ``` - fieldDefIdentifier: show_children languageCode: eng-US value: '###XXX to_bool(i % 3) XXX###' - fieldDefIdentifier: quantity languageCode: eng-US value: '###XXX to_int("42") XXX###' - fieldDefIdentifier: price languageCode: eng-US value: '###XXX to_float("19.99") XXX###' - fieldDefIdentifier: description languageCode: eng-US value: '###XXX to_string(123) XXX###' ``` - `reference` - references a specific object or resource within your application or configuration. Learn more about [migration references](https://doc.ibexa.co/en/latest/content_management/data_migration/managing_migrations/#references). ``` - fieldDefIdentifier: some_field languageCode: eng-US value: '###XXX reference("example_reference") XXX###' ``` - `project_dir` - retrieves the project's root directory path, for example to construct file paths or access project-specific resources. ``` - fieldDefIdentifier: project_directory languageCode: eng-US value: '###XXX project_dir() XXX###' ``` - `env` - retrieves the value of an environmental variable. ``` - type: user mode: update match: field: login value: admin metadata: password: '###XXX env("ADMIN_PASSWORD") XXX###' ``` #### Custom functions To add custom functionality into Migration's expression language declare it as a service and tag it with `ibexa.migrations.template.expression_language.function`. Example: ``` ibexa.migrations.template.to_bool: class: Closure factory: [ Closure, fromCallable ] arguments: - 'boolval' tags: - name: 'ibexa.migrations.template.expression_language.function' function: to_bool ibexa.migrations.template.faker: class: Closure factory: [ Closure, fromCallable ] arguments: - 'Faker\Factory::create' tags: - name: 'ibexa.migrations.template.expression_language.function' function: faker ``` Service-based functions can be also added, but they must be callable, requiring either an `__invoke` function or a wrapping service with one. ## Migration examples The following examples show what data you can import using data migrations. ### Content types The following example shows how to create a content type with two field definitions. The required metadata keys are: `identifier`, `mainTranslation`, `contentTypeGroups` and `translations`. The default values of field definition properties mirror the underlying PHP API, for example: - `translatable` defaults to `true` - `required` defaults to `false` ``` - type: content_type mode: create metadata: identifier: blog_post mainTranslation: eng-GB contentTypeGroups: - Content translations: eng-GB: name: Blog Post fields: - identifier: title type: ibexa_string required: true translations: eng-GB: name: 'Title' - identifier: body type: ibexa_richtext required: false translations: eng-GB: name: 'Body' ``` ### Content items The following example shows how to create two content items: a folder and an article inside it. When creating a content item, three metadata keys are required: `contentType`, `mainTranslation`, and `parentLocationId`. To use the location ID of the folder, which is created automatically by the system, you can use a [reference](https://doc.ibexa.co/en/latest/content_management/data_migration/managing_migrations/#references). In this case you assign the `parent_folder_location_id` reference name to the location ID, and then use it when creating the article. ``` - type: content mode: create metadata: contentType: folder mainTranslation: eng-GB location: parentLocationId: 2 fields: - fieldDefIdentifier: name languageCode: eng-GB value: 'Parent folder' references: - name: parent_folder_location_id type: location_id - type: content mode: create metadata: contentType: article mainTranslation: eng-GB location: parentLocationId: 'reference:parent_folder_location_id' fields: - fieldDefIdentifier: title languageCode: eng-GB value: 'Child article' - fieldDefIdentifier: intro languageCode: eng-GB value: xml: |
    This is article into.
    ``` Use the `delete` mode to delete content items: ``` - type: content mode: delete match: field: content_remote_id value: __REMOTE_ID__ only_visible_content: true # Optional, default: true. If set to true, only visible content will be deleted. allow_no_delete: false # Optional, default: false. If set to true, the migration will not fail if no content is found using the specified criteria. ``` ### Images The following example shows how to migrate an `example-image.png` located in `public/var/site/storage/images/3/8/3/0/383-1-eng-GB` without manually placing it in the appropriate path. To prevent the manual addition of images to specific DFS or local locations, such as `public/var/site/storage/images/` you can move image files to, for example `src/Migrations/images`. Adjust the migration file and configure the `image` field data as follows: ``` - fieldDefIdentifier: image languageCode: eng-GB value: alternativeText: '' fileName: example-image.png path: src/Migrations/images/example-image.png ``` This migration copies the image to the appropriate directory, in this case `public/var/site/storage/images/3/8/3/0/254-1-eng-GB/example-image.png`, enabling swift file migration regardless of storage (local, DFS). ### Roles The following example shows how to create a role. A role requires the `identifier` metadata key. For each policy assigned to the role, you select the module and function, with optional limitations. The following example shows the creation of a `Contributor` role: ``` - type: role mode: create metadata: identifier: Contributor policies: - module: content function: read - module: content function: create limitations: - identifier: Class values: [folder, article, blog_post] - identifier: Section values: [standard, media] - module: content function: edit limitations: - identifier: Owner values: ['1'] ``` To update an existing role, two policies' modes are available: - `replace`: (default) All existing policies are replaced by the ones from the migration. - `append`: Migration policies are added while already existing ones are kept. The following example shows how to replace the policies of the existing `Editor` role: ``` - type: role mode: update match: field: identifier value: Editor policies: - module: content function: '*' - module: user function: login limitations: - identifier: SiteAccess values: [ admin ] - module: url function: '*' ``` The following example shows the addition of a policy to the `Anonymous` role: ``` type: role mode: update match: field: identifier value: Anonymous policies: mode: append list: - module: user function: login limitations: - identifier: SiteAccess values: [ new_siteaccess ] ``` The following example shows how to delete the `Contributor` role: ``` - type: role mode: delete match: field: identifier value: Contributor ``` ### Locations The following example shows how to swap content items assigned to given locations. ``` - type: location mode: swap match1: field: location_remote_id value: f3e90596361e31d496d4026eb624c983 match2: field: location_id value: 5 ``` The metadata keys for Location are optional. The following example shows how to trash locations. ``` - type: location mode: trash match: field: location_remote_id value: f3e90596361e31d496d4026eb624c983 - type: location mode: trash match: field: location_id value: 5 ``` ### Users The following example shows how to create a user. The required metadata keys are: `login`, `email`, `password`, `enabled`, `mainLanguage`, and `contentType`. You also need to provide the user group's remote content ID. You can use an [action](https://doc.ibexa.co/en/latest/content_management/data_migration/data_migration_actions/index.md) to assign a role to the user. ``` - type: user mode: create metadata: login: janedoe email: johndoe@example.com password: Password123Password enabled: true mainLanguage: eng-GB contentType: user groups: - 3c160cca19fb135f83bd02d911f04db2 fields: - fieldDefIdentifier: first_name languageCode: eng-GB value: John - fieldDefIdentifier: last_name languageCode: eng-GB value: Doe actions: - { action: assign_user_to_role, identifier: 'Member'} ``` You can also update user information, including passwords: ``` - type: user mode: update match: field: login value: admin metadata: password: '###XXX env("ADMIN_PASSWORD") XXX###' ``` ### Languages The following example shows how to create a language. The required metadata keys are: `languageCode`, `name`, and `enabled`. ``` - type: language mode: create metadata: languageCode: ger-DE name: German enabled: true ``` ### Product catalog #### Attributes and attribute groups The following example shows how to create an attribute group with two attributes: ``` - type: attribute_group mode: create identifier: hat names: eng-GB: Hat - type: attribute mode: create identifier: size attribute_type_identifier: integer attribute_group_identifier: hat names: eng-GB: Size - type: attribute mode: create identifier: color attribute_type_identifier: selection attribute_group_identifier: hat names: eng-GB: Color options: choices: - value: red label: "eng-GB": "Red" - value: white label: "eng-GB": "White" - value: black label: "eng-GB": "Black" ``` You can also update attributes, including changing which attribute group they belong to: ``` - type: attribute mode: update criteria: type: field_value field: identifier value: width operator: '=' identifier: new_width attribute_group_identifier: size names: eng-GB: New Width ``` You can't change the attribute type of an existing attribute. ##### Date and time attributes You can manage the [date and time attribute type](https://doc.ibexa.co/en/latest/product_catalog/attributes/date_and_time/index.md) through the migrations, for example: ``` - type: attribute mode: create identifier: event_date attribute_group_identifier: example attribute_type_identifier: datetime position: 1 names: eng-GB: 'Event date' options: accuracy: day # One of: second, minute, day, month, trimester, year ``` #### Product types The following example shows how to create a product type. The main part of the migration file is the same as when creating a regular content type. A product type must also contain the definition for an `ibexa_product_specification` field. `fieldSettings` contains information about the product attributes. ``` - type: content_type mode: create metadata: identifier: hat mainTranslation: eng-GB contentTypeGroups: - product translations: eng-GB: name: Hat fields: - identifier: name type: ibexa_string required: true translations: eng-GB: name: Name - identifier: specification type: ibexa_product_specification required: true translatable: false translations: eng-GB: name: Specification fieldSettings: attributes_definitions: dimensions: - { attributeDefinition: size, required: true, discriminator: false } - { attributeDefinition: color, required: true, discriminator: true } ``` #### Products The following example shows how to create a product: ``` - type: content mode: create metadata: contentType: hat mainTranslation: eng-GB location: parentLocationId: 60 fields: - fieldDefIdentifier: name languageCode: eng-GB value: 'Top hat 58cm' - fieldDefIdentifier: specification languageCode: eng-GB value: code: top_hat__58 attributes: size: 58 is_virtual: false ``` #### Product variants The following example shows how to create variants for a product identified by its code: ``` - type: product_variant mode: create base_product_code: top_hat__58 variants: - code: top_hat__58__white attributes: color: white - code: top_hat__58__black attributes: color: black ``` #### Product assets The following example creates an image [content item](#content-items) from a local image file, and then uses it as a product asset for a variant ([created in previous example](#product-variants)): ``` - type: content mode: create metadata: contentType: image mainTranslation: eng-GB location: parentLocationId: 51 # Media/Images fields: - fieldDefIdentifier: name languageCode: eng-GB value: 'Top hat 58cm Black' - fieldDefIdentifier: image languageCode: eng-GB value: alternativeText: 'Top hat 58cm Black' fileName: 'top_hat_58cm_black.jpg' path: top_hat_58cm_black.jpg references: - name: top_hat_58cm_black_image_content_id type: content_id - type: product_asset mode: create product_code: top_hat__58__black uri: '### "ezcontent://"~reference("top_hat_58cm_black_image_content_id") ###' tags: [] ``` This migration uses a [reference](https://doc.ibexa.co/en/latest/content_management/data_migration/managing_migrations/#references) to store the created image content ID, and then uses it while creating the asset. It uses an [expression syntax](#expression-syntax) to [concat (`~`)](https://symfony.com/doc/7.4/reference/formats/expression_language.html#string-operators) the mandatory scheme `ezcontent://` and the image content ID through the [`reference` function](#built-in-functions) used on the reference's name. #### Product prices The following example shows how to create a price for a product identified by its code: ``` - type: product_price mode: create product_code: top_hat__58 currency_code: 'EUR' amount: 120 custom_prices: - customer_group: contractors base_amount: 120 custom_amount: 100 ``` #### Customer groups The following example shows how to create a customer group with a defined global price discount: ``` - type: customer_group mode: create identifier: contractors names: eng-GB: Contractors global_price_rate: -20.0 ``` #### Currencies The following example shows how to create a currency: ``` - type: currency mode: create code: TST subunits: 3 enabled: true # default, optional ``` ### Commerce (Commerce) #### Payment methods The following example shows how to create a payment method: ``` - type: payment_method mode: create paymentType: online name: - languageCode: eng-GB value: PaymentMethodName - languageCode: ger-DE value: PaymentMethodNameGerman identifier: PaymentMethodIdentifier enabled: true description: - languageCode: eng-GB value: PaymentMethodDescription - languageCode: ger-DE value: PaymentMethodDescriptionGerman ``` #### Shipping methods The following example shows how to create a shipping method: ``` - type: shipping_method mode: create name: - languageCode: eng-GB value: TestName description: - languageCode: eng-GB value: TestDescription vatCategory: standard identifier: TestIdentifier enabled: false shippingType: free options: currency: 1 price: '12.34' regions: - germany - type: shipping_method mode: create name: - languageCode: eng-GB value: TestName - languageCode: ger-DE value: TestNameGerman description: - languageCode: eng-GB value: TestDescription - languageCode: ger-DE value: TestDescriptionGerman vatCategory: standard identifier: TestIdentifier2 shippingType: flat_rate options: price: '12.34' currency: 'PLN' regions: - germany - france ``` ### Segments (Experience) (Commerce) The following example shows how to create a segment group and add segments in it: ``` - type: segment_group mode: create name: 'Contractors' identifier: contractors references: - name: contractors_group_id type: segment_group_id - type: segment mode: create name: 'Painter' identifier: painter group: identifier: contractors ``` When updating a segment group or segment, you can match the object to update by using its numerical ID or identifier: ``` - type: segment mode: update name: 'Painter and Finish' matcher: identifier: painter ``` ### Settings The following example shows how you can create and update a setting stored in the database: ``` - type: setting mode: create group: test identifier: my_setting value: first: first_value second: second_value - type: setting mode: update group: test identifier: my_setting value: first: first_value_modified ``` ### Taxonomies The following example shows how you can create a "Car" tag in the main Taxonomy: ``` - type: content mode: create metadata: contentType: tag mainTranslation: eng-GB alwaysAvailable: true section: identifier: taxonomy location: parentLocationRemoteId: taxonomy_tags_folder fields: - fieldDefIdentifier: name languageCode: eng-GB value: Car - fieldDefIdentifier: identifier languageCode: eng-GB value: car - fieldDefIdentifier: parent languageCode: eng-GB value: taxonomy_entry_identifier: root ``` The field identifiers must match the identifiers used in the `ibexa_taxonomy` configuration file. If the content type associated with the tags is changed, the configuration should be adjusted when creating migrations. > **Note: Note** > > If there are multiple taxonomies, the `taxonomy` field is then necessary here (line 21). You can use the following example to assign tags to a Content (content type Article has an additional field): ``` - type: content mode: create metadata: contentType: article mainTranslation: eng-GB alwaysAvailable: false section: identifier: standard location: parentLocationId: 42 fields: - fieldDefIdentifier: title languageCode: eng-GB value: Test1 - fieldDefIdentifier: short_title languageCode: eng-GB value: test1 - fieldDefIdentifier: author languageCode: eng-GB value: - id: '1' name: 'Administrator User' email: admin@link.invalid - fieldDefIdentifier: intro languageCode: eng-GB value: xml: |
    test
    - fieldDefIdentifier: enable_comments languageCode: eng-GB value: false - fieldDefIdentifier: field_631b169ec8035 languageCode: eng-GB value: taxonomy_entries_identifiers: - car - plane taxonomy: tags ``` When updating a content type, use: ``` - type: content_type mode: update match: field: content_type_identifier value: article fields: - identifier: field_631b169ec8035 type: ibexa_taxonomy_entry_assignment position: 7 translations: eng-GB: name: tag description: 'Tagi' required: false searchable: true infoCollector: false translatable: true category: content defaultValue: taxonomy_entries: { } taxonomy: null fieldSettings: taxonomy: tags validatorConfiguration: { } ``` ### AI action configurations - The following example shows how you can create a new action configuration in your system: ``` - type: action_configuration mode: create identifier: foo_identifier enabled: false names: 'eng-GB': 'foo_name_eng_gb' 'ger-DE': 'foo_name_ger_de' descriptions: 'ger-DE': 'foo_description_ger_de' action_handler_identifier: foo_handler action_handler_options: handler_option: foo action_type_identifier: generate_alt_text action_type_options: max_length: 130 ``` - Use the `update` mode to modify an existing action configuration: ``` - type: action_configuration mode: update match: field: identifier value: foo_identifier enabled: false identifier: bar_identifier names: 'eng-GB': 'bar_name_eng_gb' 'ger-DE': 'bar_name_ger_de' descriptions: 'ger-DE': 'bar_description_ger_de' action_handler_options: handler_option: bar action_type_options: max_length: 120 ``` - Use the `delete` mode to delete an existing action configuration: ``` - type: action_configuration mode: delete match: field: identifier value: foo_identifier ``` ### Discounts - The following example shows how you can create a new [discount](https://doc.ibexa.co/en/latest/discounts/discounts_guide/index.md) in your system: ``` - type: discount mode: create identifier: summer_sale_2025 translations: - language_code: eng-GB name: "Summer Sale 2025" description: "-10% on laptops" label: "HOT DEAL: Summer Sale!" label_description: "Get 10% off on selected items!" discount_type: cart priority: 8 enabled: true user: admin startDate: '2024-01-01T00:01:00+00:00' endDate: null createdAt: null updatedAt: null rule: type: percentage expressionValues: discount_percentage: 10 conditions: - identifier: is_in_currency expressionValues: currency_code: EUR - identifier: is_product_in_array expressionValues: product_codes: - product_code_book_0 - product_code_book_1 - identifier: is_product_in_quantity_in_cart expressionValues: quantity: 2 ``` - Use the `update` mode to modify an existing discount as in the example below. The provided conditions overwrite any already existing ones. ``` - type: discount mode: update match: field: identifier value: summer_sale_2025 identifier: summer_sale_2025_updated translations: eng-GB: language_code: eng-GB name: Updated name description: Updated description label: Updated promotion label label_description: Updated promotion description priority: 5 active: true user: admin startDate: '2024-01-01T00:00:00+00:00' endDate: null createdAt: null updatedAt: null rule: type: percentage expressionValues: discount_percentage: '10' conditions: - identifier: is_product_in_quantity_in_cart expressionValues: quantity: 2 ``` For a list of available conditions, see [Discounts API](https://doc.ibexa.co/en/latest/discounts/discounts_api/#conditions). ### Discount codes You can create a discount code as in the following example: ``` type: discount_code mode: create code: summer10 global_usage_limit: 100 # Optional user_usage_limit: 5 # Optional created_at: '2023-01-01T12:00:00+00:00' # Optional creator_id: 42 # Optional ``` ## Criteria When using `update` or `delete` modes, you can use criteria to identify the objects to operate on. > **Caution: Caution** > > Criteria only work with objects related to the product catalog. ``` type: currency mode: update criteria: type: field_value field: code value: EUR operator: '=' # default code: EEE subunits: 3 enabled: false ``` Available operators are: - `=` - `<>` - `<` - `<=` - `>` - `>=` - `IN` - `NIN` - `CONTAINS` - `STARTS_WITH` - `ENDS_WITH` You can combine criteria by using logical criteria `and` and `or`: ``` type: or criteria: - type: field_value field: code value: EUR - type: field_value field: code value: X operator: STARTS_WITH ``` Criteria can be nested. # Exporting data To see an example of migrations in action, export data already present in your installation. To export repository content, use the `ibexa:migrations:generate` command. This command generates a YAML file with the requested part of the repository. The file is located by default in the `src/Migrations/Ibexa/migrations` folder or in [a custom folder that you configure](https://doc.ibexa.co/en/latest/content_management/data_migration/managing_migrations/#migration-folders). You can later use this file to import the data. ``` php bin/console ibexa:migrations:generate --type=content --mode=create --siteaccess=admin ``` This generates a file containing all content items. Below you can see part of the output of the default Ibexa DXP installation. ``` - type: content mode: create metadata: contentType: user_group mainTranslation: eng-GB creatorId: 14 modificationDate: '2002-10-06T17:19:56+02:00' publicationDate: '2002-10-06T17:19:56+02:00' remoteId: f5c88a2209584891056f987fd965b0ba alwaysAvailable: true section: id: 2 identifier: users location: parentLocationId: 1 parentLocationRemoteId: null locationRemoteId: 3f6d92f8044aed134f32153517850f5a hidden: false sortField: 1 sortOrder: 1 priority: 0 fields: - fieldDefIdentifier: name languageCode: eng-GB value: Users - fieldDefIdentifier: description languageCode: eng-GB value: 'Main group' references: - name: ref__content__user_group__users type: content_id - name: ref__location__user_group__users type: location_id - name: ref__path__user_group__users type: path ``` The output contains all the possible information for a future migration command. Parts of it can be removed or modified. You can treat it as a template for another content item for user group. For example, you could: - Remove `references` if you don't intend to store IDs for future use (see [migration references](https://doc.ibexa.co/en/latest/content_management/data_migration/managing_migrations/#references)) - Remove `publicationDate`, `modificationDate`, `locationRemoteId`, as those are generated if not passed (like in PHP API) - Add [`actions`](https://doc.ibexa.co/en/latest/content_management/data_migration/data_migration_actions/index.md) - Add fields for other languages present in the system. Similarly, you can create update and delete operations. They're particularly functional combined with `match-property`. This option is automatically added as part of `match` expression in the update/delete migration: ``` php bin/console ibexa:migrations:generate --type=content_type --mode=update --match-property=content_type_identifier --value=article ``` ``` - type: content_type mode: update match: field: content_type_identifier value: article metadata: identifier: article mainTranslation: eng-GB modifierId: 14 modificationDate: '2012-07-24T14:35:34+00:00' remoteId: c15b600eb9198b1924063b5a68758232 urlAliasSchema: '' nameSchema: '' container: true defaultAlwaysAvailable: false defaultSortField: 1 defaultSortOrder: 1 translations: eng-GB: name: Article fields: - identifier: title type: ibexa_string position: 1 translations: eng-GB: name: Title required: true searchable: true infoCollector: false translatable: true category: '' defaultValue: 'New article' fieldSettings: { } validatorConfiguration: StringLengthValidator: maxStringLength: 255 minStringLength: null # - ... ``` You should test your migrations. See [Importing data](https://doc.ibexa.co/en/latest/content_management/data_migration/importing_data/index.md). > **Tip: Tip** > > Migration command can be executed with database rollback at the end with the `--dry-run` option. > **Caution: Caution** > > [`--siteaccess` option](#siteaccess) usage can be relevant when multiple languages or multiple repositories are used. To prevent translation loss, it's recommended that you use the SiteAccess that has all the languages used in your implementation, most likely the back office one. ## type The mandatory `--type` option defines the type of repository data to export. The following types are available: - `content` - `content_type` - `role` - `content_type_group` - `user` - `user_group` - `language` - `object_state_group` - `object_state` - `section` - `location` - `attribute_group` - `attribute` - `segment` - `segment_group` - `company` If you don't provide the `--type` option, the command asks you to select a type of data. ## mode The mandatory `--mode` option defines the action that importing the file performs. The following modes are available: - `create` - creates new items. - `update` - updates an existing item. Only covers specified fields and properties. If the item doesn't exist, causes an error. - `delete` - deletes an existing item. If the item doesn't exist, causes an error. If you don't provide the `--mode` option, the command asks you to select the mode. The following combinations of types are modes are available: | | `create` | `update` | `delete` | | -------------------- | -------- | -------- | -------- | | `content` | Yes | Yes | Yes | | `content_type` | Yes | Yes | | | `role` | Yes | Yes | Yes | | `content_type_group` | Yes | Yes | | | `user` | Yes | Yes | | | `user_group` | Yes | Yes | Yes | | `language` | Yes | | | | `object_state_group` | Yes | | | | `object_state` | Yes | | | | `section` | Yes | Yes | | | `location` | | Yes | | | `attribute_group` | Yes | Yes | Yes | | `attribute` | Yes | Yes | Yes | | `segment` | Yes | Yes | Yes | | `segment_group` | Yes | Yes | Yes | | `company` | Yes | | | ## siteaccess The optional `--siteaccess` option enables you to export (or import) data in a SiteAccess configuration's context. If not provided, the [default SiteAccess](https://doc.ibexa.co/en/latest/multisite/multisite_configuration/#default-siteaccess) is used. It's recommended that you use the SiteAccess of the target repository's back office. Specifying the SiteAccess can be mandatory, for example, when you use several SiteAccesses to handle [several languages](https://doc.ibexa.co/en/latest/multisite/languages/languages/#using-siteaccesses-for-handling-translations). Export and import commands only work with languages supported by the context SiteAccess. You must export and import with the SiteAccess supporting all the languages to preserve translations. This option is also important if you use [several repositories with their own databases](https://doc.ibexa.co/en/latest/administration/configuration/repository_configuration/#defining-custom-connection). ## match-property The optional `--match-property` option, together with `value`, enables you to select which data from the repository to export. `match-property` defines what property should be used as a criterion for selecting data. The following properties are available (per type): - `content` - `content_id` - `content_type_id` - `content_type_group_id` - `content_type_identifier` - `content_remote_id` - `location_id` - `location_remote_id` - `parent_location_id` - `user_id` - `user_email` - `user_login` - `content_type` - `content_type_identifier` - `content_type_group` - `content_type_group_id` - `content_type_group_identifier` - `language` - `language_code` - `location` - `location_remote_id` - `location_id` - `object_state` - `object_state_id` - `object_state_identifier` - `object_state_group` - `object_state_group_id` - `object_state_group_identifier` - `role` - `identifier` - `id` - `section` - `section_id` - `section_identifier` - `user` - `login` - `email` - `id` - `user_group` - `id` - `remoteId` - `attribute` - `id` - `identifier` - `type` - `attribute_group_id` - `position` - `options` - `attribute_group` - `identifier` You can extend the list of available matchers by creating [a custom one](https://doc.ibexa.co/en/latest/content_management/data_migration/add_data_migration_matcher/index.md). ## value The optional `--value` option, together with `match-property`, filters the repository content that the command exports. `value` defines which values of the `match-property` should be included in the export. For example, to export only Article content items, use the `content_type_identifier` match property with `article` as the value: ``` php bin/console ibexa:migrations:generate --type=content --mode=create --match-property=content_type_identifier --value=article ``` > **Note: Note** > > The same `match-property` and `value` is added to generated `update` and `delete` type migration files. ## file The optional `--file` option defines the name of the YAML file to export to. ``` php bin/console ibexa:migrations:generate --type=content --mode=create --file=my_data_export.yaml ``` > **Note: Note** > > When migrating multiple files at once (for example when calling `ibexa:migrations:migrate` without options), they're executed in alphabetical order. ## user-context The optional `--user-context` option enables you to run the export command as a specified user. The command only exports repository data that the selected user has access to. By default the admin account is used, unless specifically overridden by this option or in bundle configuration (`ibexa_migrations.default_user_login`). ``` php bin/console ibexa:migrations:generate --type=content --mode=create --user-context=jessica_andaya ``` # Managing migrations ## Converting migration files If you want to convert a file from the format used by the [Kaliop migration bundle](https://github.com/kaliop-uk/ezmigrationbundle) to the current migration format, use the `ibexa:migrations:kaliop:convert` command. The source file must use Kaliop mode and type combinations. The converter handles Kaliop types that are different from Ibexa types. ``` php bin/console ibexa:migrations:kaliop:convert --input=kaliop_format.yaml --output=ibexa_format.yaml ``` You can also convert multiple files using `ibexa:migrations:kaliop:bulk-convert`: ``` php bin/console ibexa:migrations:kaliop:bulk-convert --recursive kaliop_files ibexa_files ``` If you don't specify the output folder, the command overwrites the input files. ## Adding migration files Use the `ibexa:migrations:import` command to add files to the migration folder defined in configuration (by default, `src/Migrations/Ibexa/migrations`). ``` php bin/console ibexa:migrations:import my_data_export.yaml ``` ## Checking migration status To check the status of migration files in the migration folder defined in configuration, run the following command: ``` php bin/console ibexa:migrations:status ``` The command lists the migration files and indicates which of them have already been migrated. ## Migration folders The default migration folder is `src/Migrations/Ibexa/migrations`. You can configure a different folder by using the following settings: ``` ibexa_migrations: migration_directory: %kernel.project_dir%/src/Migrations/MyMigrations/ migrations_files_subdir: migration_files ``` > **Note: Multi-repository scenario** > > In multi-repository environments, where data for different websites is stored in separate databases, you can migrate such databases separately, to prevent the migration processes from affecting each other. > > The `ibexa_migrations.migration_directory` setting accepts a placeholder within a path. The placeholder is dynamically replaced by a name of the repository that you want to migrate, based on the selected SiteAccess. > > ``` > ibexa_migrations: > migration_directory: '%kernel.project_dir%/data/' > ... > ``` > > Then, when you run the migration command, you must use the [`--siteaccess` option](https://doc.ibexa.co/en/latest/content_management/data_migration/exporting_data/#siteaccess) and provide the name of the SiteAccess that you want to migrate. ## Preview configuration You can get default configuration along with option descriptions by executing the following command: ``` bin/console config:dump-reference ibexa_migrations ``` ## References References are key-value pairs necessary when one migration depends on another. Since some migrations generate object properties (like IDs) during their execution, which cannot be known in advance, references provide migrations with the ability to use previously created object properties in further migrations. They can be subsequently used by passing them in their desired place with `reference:` prefix. The example below creates the content item of type "folder" named "Media" below the root, and stores its location path as `"ref__path__folder__media"` to use it later while creating a related role. Then this reference is reused as part of a new role, as a limitation. ``` - type: content mode: create metadata: contentType: folder mainTranslation: eng-US alwaysAvailable: true section: 3 objectStates: { } location: parentLocationId: 1 hidden: false sortField: !php/const Ibexa\Contracts\Core\Repository\Values\Content\Location::SORT_FIELD_NAME sortOrder: 1 priority: 0 fields: - fieldDefIdentifier: name languageCode: eng-US value: Media # - ... actions: { } references: - name: ref__content__folder__media type: content_id - name: ref__location__folder__media type: location_id - name: ref__path__folder__media type: path - type: role mode: create metadata: identifier: foo policies: - module: content function: 'read' limitations: - identifier: Subtree values: ['reference:ref__path__folder__media'] ``` By default, references are stored in memory and can be reused within the same migration file without additional steps. To reuse them across different migration files, you can save them to disk. Reference files are located in a separate directory `src/Migrations/Ibexa/references` (for more information, see [previewing reference](#preview-configuration) `ibexa_migrations.migration_directory` and `ibexa_migrations.references_files_subdir` options). When saving references, existing files with the same name are overwritten. Reference files **aren't** loaded by default. A separate step (`type: reference`, `mode: load`, with `filename` with a relative path as value) is required. Similarly, saving a reference file is done using `type: reference`, `mode: save` step, with filename. References must be **loaded before** they can be used in the same migration file. The order of migration steps matters - they are executed sequentially from top to bottom. ``` - type: reference mode: load filename: 'references/references.yaml' # Load references created by other migrations # Use them - type: content mode: create # ... # Save any new references if needed - type: reference mode: save filename: 'references/new_references.yaml' ``` ## Available reference types - `content` - content_id - location_id - path - `content_type` - content_type_id - `language` - language_id - `role` - role_id - `user_group` - user_group_id # Data migration actions Some migration steps can contain a special `actions` property. You can find which migration steps support actions in the table below: | | `create` | `update` | `delete` | | -------------- | -------- | -------- | -------- | | `content` | Yes | Yes | Yes | | `content_type` | Yes | Yes | Yes | | `role` | Yes | Yes | | | `user` | Yes | Yes | | | `user_group` | Yes | Yes | | | `company` | Yes | | | Actions are optional operations that can be run after the main "body" of a migration has been executed (for example, content has been created / updated, object state has been added). Their purpose is to allow additional operations to be performed as part of this particular migration. They're executed inside the same transaction, so in the event of failure they cause database rollback to occur. For example, when updating a content type object, some fields might be removed: ``` - type: content_type mode: update match: field: content_type_identifier value: article actions: - { action: assign_content_type_group, value: 'Media' } - { action: unassign_content_type_group, value: 'Content' } - { action: remove_field_by_identifier, value: 'short_title' } - { action: remove_drafts, value: null } ``` When executed, this migration: - Finds content type using its identifier (`article`) - Assigns content type group "Media" - Removes it from content type group "Content" - Removes the `short_title` field - Removes its existing drafts, if any. ## Available migration actions The following migration actions are available out of the box: - `assign_object_state` (Content Create) - `assign_parent_location` (Content Create / Update) - `assign_section` (Content Update) - `hide` (Content Create / Update) - `reveal` (Content Create / Update) - `assign_content_type_group` (Content type Create / Update) - `remove_drafts` (Content type Update) - `remove_field_by_identifier` (Content type Update) - `unassign_content_type_group` (Content type Update) - `add_block_to_available_blocks` (Content type Update) - `assign_role_to_user` (Role Create / Update) - `assign_role_to_user_group` (Role Create / Update) - `assign_user_to_role` (User Create / Update) - `assign_user_group_to_role` (User group Create / Update) - `unassign_role_user_group` (User group Update) In contrast with Kaliop migrations, actions provide you with ability to perform additional operations and extend the migration functionality. For more information, see [creating your own Actions](https://doc.ibexa.co/en/latest/content_management/data_migration/create_data_migration_action/index.md). ## Action usage examples ### Content mode: Create ``` actions: - { action: assign_object_state, identifier: locked, groupIdentifier: ibexa_lock } - { action: assign_parent_location, value: 2 } - { action: hide } ``` mode: Update ``` actions: - { action: assign_parent_location, value: 2 } - { action: assign_section, id: 4 } - { action: assign_section, identifier: 'media' } ``` ### Content types mode: Create ``` actions: - { action: assign_content_type_group, value: 'Media' } ``` mode: Update ``` actions: - { action: assign_content_type_group, value: 'Media' } - { action: unassign_content_type_group, value: 'Content' } - { action: remove_field_by_identifier, value: 'short_title' } - { action: remove_drafts, value: null } - { action: add_block_to_available_blocks, fieldDefinitionIdentifier: 'page', blocks: ['event'] } ``` ### Roles mode: Create and Update ``` actions: - action: assign_role_to_user_group remote_id: 'remote_id_152454854' - action: assign_role_to_user_group id: 42 - action: assign_role_to_user id: 42 - action: assign_role_to_user email: 'mail@invalid.c' - action: assign_role_to_user login: foo ``` ### Users mode: Create and Update ``` actions: - action: assign_user_to_role identifier: foo - action: assign_user_to_role id: 2 - action: assign_user_to_role id: 2 limitation: type: Section values: - 1 ``` ### User groups mode: Create and Update ``` actions: - action: assign_user_group_to_role identifier: Editor - action: assign_user_group_to_role id: 2 - action: assign_user_group_to_role id: 1 limitation: type: Section values: - 1 ``` > **Note: Note** > > In the `assign_user_group_to_role` action, limitation type section can only use section ID. mode: Update ``` actions: - action: unassign_role_user_group id: 1 ``` > **Note: Note** > > In the `unassign_role_user_group` action, the ID is role assignment ID from the `ibexa_user_role` table. # Create data migration step Besides the [built-in migrations steps](https://doc.ibexa.co/en/latest/content_management/data_migration/importing_data/#available-migrations), you can also create custom ones. To create a custom migration step, you need: - A step class, to store any additional data that you might require. - A step normalizer, to convert YAML definition into your step class. - A step executor, to handle the step. The following example shows how to create a step that replaces all `ibexa_string` fields that have an old company name with "New Company Name". ## Create step class First, create a step class, in `src/Migrations/Step/ReplaceNameStep.php`: ``` replacement = $replacement ?? 'New Company Name'; } public function getReplacement(): string { return $this->replacement; } } ``` ## Create normalizer Then you need a normalizer to convert data that comes from YAML into a step object, in `src/Migrations/Step/ReplaceNameStepNormalizer.php`: ``` */ final class ReplaceNameStepNormalizer extends AbstractStepNormalizer { protected function normalizeStep( StepInterface $object, ?string $format = null, array $context = [] ): array { assert($object instanceof ReplaceNameStep); return [ 'replacement' => $object->getReplacement(), ]; } protected function denormalizeStep( $data, string $type, string $format, array $context = [] ): ReplaceNameStep { return new ReplaceNameStep($data['replacement'] ?? null); } public function getHandledClassType(): string { return ReplaceNameStep::class; } public function getType(): string { return 'company_name'; } public function getMode(): string { return 'replace'; } } ``` Then, tag the step normalizer, so it's recognized by the serializer used for migrations. ``` App\Migrations\Step\ReplaceNameStepNormalizer: tags: - 'ibexa.migrations.serializer.step_normalizer' - 'ibexa.migrations.serializer.normalizer' ``` ## Create executor And finally, create an executor to perform the step, in `src/Migrations/Step/ReplaceNameExecutor.php`: ``` contentService->find(new Filter()); foreach ($contentItems as $contentItem) { $struct = $this->contentService->newContentUpdateStruct(); foreach ($contentItem->getFields() as $field) { if ($field->fieldTypeIdentifier !== 'ibexa_string') { continue; } if ($field->fieldDefIdentifier === 'identifier') { continue; } if (str_contains((string) $field->value, 'Company Name')) { $newValue = str_replace('Company Name', $step->getReplacement(), $field->value); $struct->setField($field->fieldDefIdentifier, new Value($newValue)); } } try { $content = $this->contentService->createContentDraft($contentItem->contentInfo); $content = $this->contentService->updateContent($content->getVersionInfo(), $struct); $this->contentService->publishVersion($content->getVersionInfo()); } catch (\Throwable) { // Ignore } } return null; } public function canHandle(StepInterface $step): bool { return $step instanceof ReplaceNameStep; } } ``` Tag the executor with `ibexa.migrations.step_executor` tag. ``` App\Migrations\Step\ReplaceNameStepExecutor: tags: - 'ibexa.migrations.step_executor' ``` Then you can create a migration file that represents this step in your application: ``` - type: company_name mode: replace replacement: 'New Company Name' # as declared in normalizer, this is optional ``` # Create data migration action To create an [action](https://doc.ibexa.co/en/latest/content_management/data_migration/data_migration_actions/index.md) that is performed after a migration step, you need: - An action class, to store any additional data that you might require. - An action denormalizer, to convert YAML definition into your action class. - An action executor, to handle the action. The following example shows how to create an action that assigns a content item to a Section. First, create an action class, in `src/Migrations/Action/AssignSection.php`: ``` sectionIdentifier; } public function getSupportedType(): string { return self::TYPE; } } ``` Then you need a denormalizer to convert data that comes from YAML into an action object, in `src/Migrations/Action/AssignSectionDenormalizer.php`: ``` $data * @param string $type * @param string|null $format * @param array $context * * @return \App\Migrations\Action\AssignSection */ public function denormalize($data, string $type, ?string $format = null, array $context = []): AssignSection { Assert::keyExists($data, 'value'); return new AssignSection($data['value']); } } ``` Then, tag the action denormalizer so it's recognized by the serializer used for migrations. ``` services: App\Migrations\Action\AssignSectionDenormalizer: autoconfigure: false tags: - { name: 'ibexa.migrations.serializer.normalizer' } ``` And finally, add an executor to perform the action, in `src/Migrations/Action/AssignSectionExecutor.php`: ``` contentService->loadContentInfo($content->id); $section = $this->sectionService->loadSectionByIdentifier($action->getValue()); $this->sectionService->assignSection($contentInfo, $section); } } ``` Tag the executor with `ibexa.migrations.executor.action.` tag, where `` is the "type" of the step that executor works with (for example, `content`, `content_type`, or `location`). The tag has to have a `key` property with the action type. ``` App\Migrations\Action\AssignSectionExecutor: tags: - { name: 'ibexa.migrations.executor.action.content', key: !php/const App\Migrations\Action\AssignSection::TYPE } ``` # Create data migration matcher [Matchers in data migrations](https://doc.ibexa.co/en/latest/content_management/data_migration/exporting_data/#match-property) enable you to select which data from the repository to export. In addition to the built-in matchers, you can create custom matchers for content. The following example creates a matcher for section identifiers. ## Create normalizer To do this, first add a normalizer which handles the conversion between objects and the YAML format used for data migration. Matchers are instances of `FilteringCriterion`, so a custom normalizer needs to denormalize into an instance of `FilteringCriterion`. > **Tip: Normalizers** > > To learn more about normalizers, refer to [Symfony documentation](https://symfony.com/doc/7.4/serializer.html). Create the normalizer in `src/Migrations/Matcher/SectionIdentifierNormalizer.php`: ``` $data * @param array $context */ protected function createCriterion(array $data, string $type, ?string $format, array $context): FilteringCriterion { Assert::keyExists($data, 'value'); return new Criterion\SectionIdentifier($data['value']); } public function supportsNormalization($data, ?string $format = null, array $context = []): bool { return $data instanceof Criterion\SectionIdentifier; } } ``` Register the normalizer as a service: ``` App\Migrations\Matcher\SectionIdentifierNormalizer: tags: - { name: 'ibexa.migrations.serializer.normalizer' } ``` > **Note: Normalizer order** > > User-defined normalizers are always executed before the built-in ones. However, you can additionally set the priority of your normalizers. > > Check the priorities of all normalization services by using: > > ``` > php bin/console debug:container --tag ibexa.migrations.serializer.normalizer > ``` ## Create generator Additionally, if you want to export data using the `ibexa:migrations:generate` command, you need a generator. Create the generator in `src/Migrations/Matcher/SectionIdentifierGenerator.php`: ``` migrationService->listMigrations() as $migration) { $output->writeln($migration->getName()); } ``` To get a single migration file by its name, use the `MigrationService:findOneByName()` method: ``` $my_migration = $this->migrationService->findOneByName($migration_name); ``` ## Running migration files To run migration file(s), use either `MigrationService:executeOne()` or `MigrationService:executeAll()`: ``` $this->migrationService->executeOne($my_migration); $this->migrationService->executeAll('admin'); ``` Both `executeOne()` and `executeAll()` can take an optional parameter: the login of the User that you want to execute the migrations as. ## Adding new migrations To add a new migration file, use the `MigrationService:add()` method: ``` $this->migrationService->add( new Migration( 'new_migration.yaml', $string_with_migration_content ) ); ``` # Field types Field types are the smallest building blocks of content. Ibexa DXP comes with many [built-in field types](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/field_type_reference/#available-field-types) that cover most common needs, for example, Text line, Email address, Author list, Content relation, Map location, or Float. Field types are responsible for: - Storing data, either using the native storage engine mechanisms or specific means - Validating input data - Making the data searchable (if applicable) - Displaying fields of this type ## Custom data Ibexa DXP can support custom data to be stored in the fields of a content item. To do so, you need to create a custom field type. A custom field type must implement the **FieldType Service Provider Interfaces** available in the [`Ibexa\Core\FieldType`](https://github.com/ibexa/core/tree/main/src/lib/FieldType) namespace. > **Note: Registration** > > Remember that all your custom field types must be registered in `config/services.yml`. For more information, see [Registration](https://doc.ibexa.co/en/latest/content_management/field_types/type_and_value/#registration). To provide custom functionality for a field type, the SPI interacts with multiple layers of the Ibexa DXP architecture: *[Image: Field type Overview]* On the top layer, the field type needs to provide conversion from and to a simple PHP hash value to support the **REST API**. The generated hash value may only consist of scalar values and hashes. It must not contain objects or arrays with numerical indexes that aren't sequential and/or don't start with zero. > **Caution: Simple hash values** > > A simple hash value always means an array of scalar values and/or nested arrays of scalar values. To avoid issues with format conversion, don't use objects inside the simple hash values. Below that, the field type must support the **public PHP API** implementation regarding: - Settings definition for `FieldDefinition` - Value creation and validation - Communication with the Persistence SPI On the bottom level, a field type can additionally hook into the **Persistence SPI** to store data from a `FieldValue` in an external service. All non-standard Ibexa DXP database tables (for example, `ibexa_url`) are treated as [external storage](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_storage/#storing-data-externally). The following sequence diagrams visualize the process of creating and publishing new content across all layers, especially focused on the interaction with a field type. ## Creating content *[Image: Create content sequence]* ## Publishing content > **Note: indexLocation()** > > For **Solr** locations are indexed during Content indexing. For **Legacy/SQL** indexing isn't required as location data already exists in a database. *[Image: Publish content sequence]* ## Updating content *[Image: Update content sequence]* ## Loading content *[Image: Load content sequence]* # Type and Value A field type must contain a Type class which contains the logic of the field type, for example, validating data, transforming from various formats, or describing the validators. A Type class must implement `Ibexa\Core\FieldType\FieldType` ("field type interface"). All native field types also extend the `Ibexa\Core\FieldType\FieldType` abstract class that implements this interface and provides implementation facilities through a set of abstract methods of its own. You should also provide a value object class for storing the custom field value provided by the field type. The Value is used to represent an instance of the field type within a content item. Each field presents its data using an instance of the Type's Value class. A Value class must implement the `Ibexa\Contracts\Core\FieldType` interface. It may also extend the `Ibexa\Core\FieldType\Value` abstract class. It's meant to be stateless and as lightweight as possible. This class must contain as little logic as possible, because the logic is handled by the Type class. ## Type class The Type class of a field type provides an implementation of the [`Ibexa\Contracts\Core\FieldType\FieldType`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-FieldType-FieldType.html) interface. ### Field Definition handling A custom field type is used in a field definition of a custom content type. You can additionally provide [settings for the field type](#field-type-settings) and a [validator configuration](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_validation/index.md). Since the public PHP API cannot know anything about these, their handling is delegated to the field type itself through the following methods: #### `getFieldTypeIdentifier()` Returns a unique identifier for the custom field type which is used to assign the type to a field definition. By convention it should be prefixed by a unique vendor shortcut (for example, `ibexa` for Ibexa DXP). #### `getSettingsSchema()` This method retrieves via public PHP API a schema for the field type settings. A typical setting would be, for example, default value. The settings structure defined by this schema is stored in the `FieldDefinition`. Since it's not possible to define a generic format for such a schema, the field type is free to return any serializable data structure from this method. #### `getValidatorConfigurationSchema()` In addition to normal settings, the field type should provide schema settings for its validation process. The schema describes what kind of validation can be performed by the field type and which settings the user can specify to these validation methods. For example, the `ibexa_string` type can validate minimum and maximum length of the string. It therefore provides a schema to indicate to the user that they might specify the corresponding restrictions, when creating a `FieldDefinition` with this type. The schema doesn't underlie any regulations, except for that it must be serializable. #### `validateFieldSettings()` The type is asked to validate the settings (provided by the user) before the public PHP API stores those settings for the field type in a `FieldDefinition`. As a result, the field type must return if the given settings comply to the schema defined by `getSettingsSchema()`. #### `validateValidatorConfiguration()` As in `validateFieldSettings()`, this method verifies that the given validator configuration complies to the schema provided by `getValidatorConfigurationSchema()`. It's important to know that the schema definitions of the field type can be both of arbitrary and serializable format. It's highly recommended to use a simple hash structure. > **Note: Note** > > Since it's not possible to enforce a schema format, the code using a specific field type must basically know all field types it deals with. This also applies to all user interfaces and the REST API, which therefore must provide extension points to register handling code for custom field type. These extensions aren't defined yet. ### Field type name The content item name is retrieved by the `Ibexa\Core\FieldType\FieldType::getName` method which must be implemented. To generate content item name or URL alias the field type name must be a part of a name schema or a URL schema. ## Value handling A field type needs to deal with the custom value format provided by it. In order for the public PHP API to work properly, it delegates working with such custom field values to the corresponding field type. The `Ibexa\Core\FieldType\FieldType` interface therefore provides the following methods: #### `acceptValue()` This method is responsible for accepting and converting user input for the field. It checks the input structure by accepting, building, and returning a different structure holding the data. For example: a user provides an HTTP link as a string, `acceptValue()` converts the link to a URL field type value object. Unlike the `FieldType\Value` constructor, it's possible to make this method aware of multiple input types (object or primitive). > **Note: Note** > > `acceptValue()` asserts structural consistency of the value, but doesn't validate plausibility of the value. #### `getEmptyValue()` The field type can specify that the user may define a default value for the `Field` of the type through settings. If no default value is provided, the field type is asked for an "empty value" as the final fallback. The value chain for filling a specific field of the field type is as follows: 1. Is a value provided by the filling user? 2. If not, is a default value provided by the`FieldDefinition`? 3. If not, take the empty value provided by the `FieldType`. #### `validate()` In contrast to `acceptValue()` this method validates the plausibility of the given value. It's based on the field type settings and validator configuration and stored in the corresponding `FieldDefinition`. ### Serialization When [REST API](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_usage/rest_api_usage/index.md) is used, conversion needs to be done for field type values, settings, and validator configurations. These are converted to and from a simple hash format that can be encoded in REST payload. As conversion needs to be done both when transmitting and receiving data through REST, field type implements the following pairs of methods: | Method | Description | | ---------------------------------- | -------------------------------------------------------------------- | | `toHash()` | Converts field type Value into a simple hash format. | | `fromHash()` | Converts the other way around. | | `fieldSettingsToHash()` | Converts field type settings to a simple hash format. | | `fieldSettingsFromHash()` | Converts the other way around. | | `validatorConfigurationToHash()` | Converts field type validator configuration to a simple hash format. | | `validatorConfigurationFromHash()` | Converts the other way around. | > **Caution: Simple hash values** > > A simple hash value always means an array of scalar values and/or nested arrays of scalar values. To avoid issues with format conversion, don't use objects inside the simple hash values. ## Registration The field type must be registered in `config/services.yml`: ``` services: Ibexa\FieldTypeMatrix\FieldType\Type: parent: Ibexa\Core\FieldType\FieldType tags: - {name: ibexa.field_type, alias: ibexa_matrix} ``` #### `parent` As described in the [Symfony service container documentation](https://symfony.com/doc/7.4/service_container/parent_services.html), the `parent` config key indicates that you want your service to inherit from the parent's dependencies, including constructor arguments and method calls. This helps to avoid repetition in your field type configuration and keeps consistency between all field types. If you need to inject other services into your Type class, skip using the `parent` config key. #### `tags` Like most API components, field types use the [Symfony service tag mechanism](https://symfony.com/doc/7.4/service_container/tags.html). A service can be assigned one or several tags, with specific parameters. When the [service container](https://doc.ibexa.co/en/latest/api/php_api/php_api/#service-container) is compiled into a PHP file, tags are read by `CompilerPass` implementations that add extra handling for tagged services. Each service tagged as `ibexa.field_type` is added to a [registry](https://martinfowler.com/eaaCatalog/registry.html) using the `alias` key as its unique `fieldTypeIdentifier`, for example, `ibexa_string`. Each field type must also inherit from the abstract `ibexa.field_type` service. This ensures that the initialization steps shared by all field types are executed. > **Tip: Tip** > > The configuration of built-in field types is located in [`core/src/lib/Resources/settings/fieldtypes.yml`](https://github.com/ibexa/core/blob/main/src/lib/Resources/settings/fieldtypes.yml). ### Indexing To make the search engine aware of the data stored in a field type, register it as [indexable](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_search/index.md) ## Field type settings It's recommended to use a simple associative array format for the settings schema returned by `Ibexa\Contracts\Core\FieldType\FieldType::getSettingsSchema()`, which follows these rules: - The key of the associative array identifies a setting (for example, `default`) - Its value is an associative array describing the setting using: - `type` to identify the setting type (for example, `int` or `string`) - `default` containing the default setting value An example schema could look like this: ``` [ 'backupData' => [ 'type' => 'bool', 'default' => false ], 'defaultValue' => [ 'type' => 'string', 'default' => 'Default Value' ] ]; ``` The settings are mapped into Symfony forms via the [FormMapper](https://doc.ibexa.co/en/latest/content_management/field_types/form_and_template/#formmapper). > **Note: Note** > > You can store field type settings internally, or, when the schema becomes too complex, move them to [external storage](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_storage/#storing-field-type-settings-externally). ## Extensibility points Some field types require additional processing, for example a field type storing a binary file, or one having more complex settings, or validator configuration. For this purpose specific implementations of an abstract class `Ibexa\Contracts\Rest\FieldTypeProcessor` are used. This class provides the following methods: | Method | Description | | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `preProcessValueHash()` | Performs manipulations on a received value hash, so that it conforms to the format expected by the `fromHash()` method described above. | | `postProcessValueHash()` | Performs manipulations on a outgoing value hash, previously generated by the `toHash()` method described above. | | `preProcessFieldSettingsHash()` | Performs manipulations on a received settings hash, so that it conforms to the format expected by the `fieldSettingsFromHash()` method described above. | | `postProcessFieldSettingsHash()` | Performs manipulations on a outgoing settings hash, previously generated by the `fieldSettingsToHash()` method described above. | | `preProcessValidatorConfigurationHash()` | Performs manipulations on a received validator configuration hash, so that it conforms to the format expected by the `validatorConfigurationFromHash()` method described above. | | `postProcessValidatorConfigurationHash()` | Performs manipulations on a outgoing validator configuration hash, previously generated by the `validatorConfigurationToHash()` method described above. | Base implementations of these methods return the given hash, so you can implement only the methods your field type requires. Some built-in field types already implement processors and you're encouraged to take a look at them. # Form and template ## FormMapper The FormMapper maps field definitions into Symfony forms, allowing field editing. It can implement two interfaces: - `Ibexa\Contracts\ContentForms\FieldType\FieldValueFormMapperInterface` to provide editing support - `Ibexa\AdminUi\FieldType\FieldDefinitionFormMapperInterface` to provide field type definition editing support, when you require non-standard settings ### FieldValueFormMapperInterface The `FieldValueFormMapperInterface::mapFieldValueForm` method accepts two arguments: - `FormInterface` — form for the current field - `FieldData` — underlying data for current field form You have to add your form type to the content editing form. The example shows how `ibexa_boolean` injects the form: ``` use Ibexa\Contracts\ContentForms\Data\Content\FieldData; use Ibexa\ContentForms\Form\Type\FieldType\CheckboxFieldType; use Symfony\Component\Form\FormInterface; public function mapFieldValueForm(FormInterface $fieldForm, FieldData $data) { $fieldDefinition = $data->fieldDefinition; $formConfig = $fieldForm->getConfig(); $fieldForm ->add( $formConfig->getFormFactory()->createBuilder() ->create( 'value', CheckboxFieldType::class, [ 'required' => $fieldDefinition->isRequired, 'label' => $fieldDefinition->getName( $formConfig->getOption('languageCode') ), ] ) ->setAutoInitialize(false) ->getForm() ); } ``` Your type has to be called `value`. In the example above, `CheckboxFieldType::class` is used, but you can use standard Symfony form type instead. It's good practice to encapsulate fields with custom types as it allows easier templating. Type has to be compatible with your field type's `Ibexa\Core\FieldType` implementation. You can use a [`DataTransformer`](https://symfony.com/doc/7.4/form/data_transformers.html) to achieve that or assure correct property and form field names. ### FieldDefinitionFormMapperInterface Providing definition editing support is almost identical to creating content editing support. The only difference are field names: ``` use Ibexa\AdminUi\Form\Data\FieldDefinitionData; use Ibexa\ContentForms\Form\Type\FieldType\CountryFieldType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\FormInterface; public function mapFieldDefinitionForm(FormInterface $fieldDefinitionForm, FieldDefinitionData $data) { $fieldDefinitionForm ->add( 'isMultiple', CheckboxType::class, [ 'required' => false, 'property_path' => 'fieldSettings[isMultiple]', 'label' => 'field_definition.ibexa_country.is_multiple', ] ) ->add( $fieldDefinitionForm->getConfig()->getFormFactory()->createBuilder() ->create( 'defaultValue', CountryFieldType::class, [ 'choices_as_values' => true, 'multiple' => true, 'expanded' => false, 'required' => false, 'label' => 'field_definition.ibexa_country.default_value', ] ) // Deactivate auto-initialize as you're not on the root form. ->setAutoInitialize(false)->getForm() ); } ``` Use names corresponding to the keys used in field type's `Ibexa\Core\FieldType\FieldType::$settingsSchema` implementation. The special `defaultValue` key allows you to specify a field for setting the default value assigned during content editing. ### Registering the service The FormMapper must be registered as a service: ``` App\FieldType\Mapper\CustomFieldTypeMapper: tags: - { name: ibexa.admin_ui.field_type.form.mapper.definition, fieldType: custom } - { name: ibexa.admin_ui.field_type.form.mapper.value, fieldType: custom } ``` Tag the mapper according to the support you need to provide: - Add the `ibexa.admin_ui.field_type.form.mapper.value` tag when providing content editing support (`FieldValueFormMapperInterface` interface). - Add the `ibexa.admin_ui.field_type.form.mapper.definition` tag when providing field type definition editing support (`FieldDefinitionFormMapperInterface` interface). The `fieldType` key has to correspond to the name of your field type. ## Content view templates To render the field in content view by using the [`ibexa_render_field()` Twig helper](https://doc.ibexa.co/en/latest/templating/twig_function_reference/field_twig_functions/#ibexa_render_field), you need to define a template containing a block for the field. ``` {% block customfieldtype_field %} {# Your code here #} {% endblock %} ``` By convention, your block must be named `_field`. > **Tip: Tip** > > Template blocks for built-in field types are available in [`Core/Resources/views/content_fields.html.twig`](https://github.com/ibexa/core/blob/main/src/bundle/Core/Resources/views/content_fields.html.twig). > > This template is also exposed as a part of Standard Design, so you can override it with the [design engine](https://doc.ibexa.co/en/latest/templating/design_engine/design_engine/index.md). To do so, place the template `themes/standard/content_fields.html.twig` in your `Resources/views` (assuming `ibexa_standard_design.override_kernel_templates` is set to true). ### Template variables The block can receive the following variables: | Name | Type | Description | | --------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `field` | `Ibexa\Contracts\Core\Repository\Values\Content\Field` | The field to display | | `contentInfo` | `Ibexa\Contracts\Core\Repository\Values\Content\ContentInfo` | The ContentInfo of the content item the field belongs to | | `versionInfo` | `Ibexa\Contracts\Core\Repository\Values\Content\VersionInfo` | The VersionInfo of the content item the field belongs to | | `fieldSettings` | array | Settings of the field (depends on the field type) | | `parameters` | hash | Options passed to `ibexa_render_field()` under the `'parameters'` key | | `attr` | hash | The attributes to add the generate the HTML markup, passed to ibexa_render_field()`under the`'attr'\` key. Contains at least a class entry, containing -field | ### Reusing blocks For easier field type template development you can take advantage of all defined blocks by using the [`block()` function](https://twig.symfony.com/doc/3.x/functions/block.html). You can for example use `simple_block_field`, `simple_inline_field` or `field_attributes` blocks provided in [`content_fields.html.twig`](https://github.com/ibexa/core/blob/main/src/bundle/Core/Resources/views/content_fields.html.twig#L486). > **Caution: Caution** > > To be able to reuse built-in blocks, your template must inherit from `@IbexaCore/content_fields.html.twig`. ### Registering a template If you don't use the [design engine](https://doc.ibexa.co/en/latest/templating/design_engine/design_engine/index.md) or you want to have separate templates per field type and/or SiteAccess, you can register a template under the `ibexa.system..field_templates` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa: system: : field_templates: - template: 'fields/custom_field_template.html.twig' # Priority is optional (default is 0). The higher it is, the higher your template gets in the list. priority: 10 ``` ## Back office templates ### Back office view template For templates for previewing the field in the back office, using the design engine is recommended with `ibexa_standard_design.override_kernel_templates` set to `true`. With the design engine you can apply a template (for example, `Resources/views/themes/admin/content_fields.html.twig`) without any extra configuration. If you don't use the design engine, apply the following configuration: ``` ibexa: systems: admin_group: field_templates: - { template: 'adminui/field/custom_field_view.html.twig', priority: 10 } ``` ### Field edit template To use a template for the field edit form in the back office, you need to specify it in configuration under the `twig.form_themes` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` twig: form_themes: - 'adminui/field/custom_field_template.html.twig' ``` We encourage using custom form types for encapsulation as this makes templating easier by providing Twig block name. All built-in field types are implemented with this approach. In that case overriding form theme can be done with: ``` {% block custom_fieldtype_widget %} Hello world! {{ block('form_widget') }} {% endblock %} ``` For more information on creating and overriding form type templates, see [Symfony documentation](https://symfony.com/doc/7.4/form/create_custom_field_type.html#creating-the-form-type-template). # Field type storage ## Storage conversion If you want to store field values in regular Ibexa DXP database tables, the `FieldValue` must be converted to the storage-specific format used by the Persistence SPI: `Ibexa\Contracts\Core\Persistence\Content\FieldValue`. After restoring a field of the field type, you must reverse the conversion. The following methods of the field type are responsible for that: | Method | Description | | ------------------------ | ----------------------------------------------------------------------------------------------------------------- | | `toPersistenceValue()` | This method receives the value of a field of the field type and returns an SPI `FieldValue`, which can be stored. | | `fromPersistenceValue()` | This method receives an SPI `FieldValue` and reconstructs the original value of the field from it. | The SPI `FieldValue` struct has properties which the field type can use: | Property | Description | | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `$data` | The data to be stored in the database. This may be a scalar value, an associative array or a simple, serializable object. | | `$externalData` | The arbitrary data stored in this field isn't touched by any of the Ibexa DXP components directly, but is available for [Storing data externally](#storing-data-externally). | | `$sortKey` | A value which can be used to sort content by this field. | ### Legacy storage engine The Legacy storage engine uses the `ibexa_content_field` table to store field values, and `ibexa_content_type_field_definition` to store field definition values. They're both based on the same principle. Each row represents a field or a field definition, and offers several free fields of different types, where the type can store its data. - `ibexa_content_field` offers: - `data_int` - `data_text` - `data_float` - `ibexa_content_type_field_definition` offers: - four `data_int` (`data_int1` to `data_int4`) fields - four `data_float` (`data_float1` to `data_float4`) ones - five `data_text` (`data_text1` to `data_text5`) Each type is free to use those fields in any way it requires. The default Legacy storage engine cannot store arbitrary value information as provided by a field type. This means that using this storage engine requires a conversion. Converters map a field's semantic values to the fields described above, for both settings (validation and configuration) and value. The conversion takes place through the `Ibexa\Core\Persistence\Legacy\Content\FieldValue\Converter` interface, which you must implement in your field type. The interface contains the following methods: | Method | Description | | ---------------------------- | ------------------------------------------------------------------------------------------------- | | `toStorageValue()` | Converts a Persistence `Value` into a Legacy storage specific value. | | `toFieldValue()` | Converts the other way around. | | `toStorageFieldDefinition()` | Converts a Persistence `FieldDefinition` to a storage specific one. | | `toFieldDefinition` | Converts the other way around. | | `getIndexColumn()` | Returns the storage column which is used for indexing either `sort_key_string` or `sort_key_int`. | Just like a Type, a Legacy Converter needs to be registered and tagged in the [service container](https://doc.ibexa.co/en/latest/api/php_api/php_api/#service-container). #### Registering a converter The registration of a `Converter` currently works through the `$config` parameter of [`Ibexa\Core\Persistence\Legacy\Handler`](https://github.com/ibexa/core/blob/main/src/lib/Persistence/Legacy/Handler.php). Those converters also need to be correctly exposed as services and tagged with `ibexa.field_type.storage.legacy.converter`: ``` services: Ibexa\Core\Persistence\Legacy\Content\FieldValue\Converter\TextLine: tags: - {name: ibexa.field_type.storage.legacy.converter, alias: ibexa_string} ``` The tag has the following attribute: | Attribute name | Usage | | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `alias` | Represents the `fieldTypeIdentifier` (like for the [field type service](https://doc.ibexa.co/en/latest/content_management/field_types/type_and_value/#registration)). | > **Tip: Tip** > > Converter configuration for built-in field types is located in [`ibexa/core/src/lib/Resources/settings/fieldtype_external_storages.yml`](https://github.com/ibexa/core/blob/main/src/lib/Resources/settings/fieldtype_external_storages.yml). ## Storing data externally A field type may store arbitrary data in external data sources. External storage can be, for example, a web service, a file in the file system, another database or even the Ibexa DXP database itself (in form of a non-standard table). To store data in external storage, the field type interacts with the Persistence SPI through the `Ibexa\Contracts\Core\FieldType\FieldStorage` interface. Accessing the internal storage of a content item that includes a field of the field type calls one of the following methods to also access the external data: | Method | Description | | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `hasFieldData()` | Returns whether the field type stores external data at all. | | `storeFieldData()` | Called right before a field of the field type is stored. The method stores `$externalData`. It returns `true` if the call manipulated internal data of the given field, so that it's updated in the internal database. | | `getFieldData()` | Called after a field has been restored from the database to restore `$externalData`. | | `deleteFieldData()` | Must delete external data for the given field, if exists. | | `getIndexData()` | Returns the actual index data for the provided `Ibexa\Contracts\Core\Persistence\Content\Field`. For more information, see [search service](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_search/#search-field-values). | Each of the above methods (except `hasFieldData`) receives a `$context` array with information on the underlying storage and the environment. To retrieve and store data in the Ibexa DXP data storage, but outside of the normal structures (for example, a custom table in an SQL database), use [Gateway-based storage](#gateway-based-storage) with properly injected Doctrine Connection. The field type must take care on its own for being compliant with different data sources and that third parties can extend the data source support. ### Gateway-based storage To allow the usage of a field type that uses external data with different data storages, it's recommended to implement a gateway infrastructure and a registry for the gateways. To make this easier, the Core implementation of field types provides corresponding interfaces and base classes. They can also be used for custom field types. The interface `Ibexa\Contracts\Core\FieldType\StorageGateway` is implemented by gateways, to be handled correctly by the registry. It has one method: | Method | Description | | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `setConnection()` | The registry mechanism uses this method to set the SPI storage connection (for example, the database connection to the Legacy Storage database) into the gateway, which might be used to store external data. The connection is retrieved from the `$context` array automatically by the registry. | The Gateway implementation itself must take care of validating that it received a usable connection. If it doesn't, it should throw a `RuntimeException`. The registry mechanism is realized as a base class for `FieldStorage` implementations: `Ibexa\Core\FieldType\GatewayBasedStorage`. For managing `StorageGateway`s, the following methods are already implemented in the base class: | Method | Description | | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `addGateway()` | Allows the registration of additional `StorageGateway`s from the outside. Furthermore, an associative array of `StorageGateway`s can be given to the constructor for basic initialization. This array should originate from the dependency injection mechanism. | | `getGateway()` | This protected method is used by the implementation to retrieve the correct `StorageGateway` for the current context. | > **Tip: Tip** > > Refer to the built-in Keyword, URL and User field types for usages of such infrastructure. ### Registering external storage To use external storage, you need to define a service implementing the `Ibexa\Contracts\Core\FieldType\FieldStorage` interface and tag it as `ibexa.field_type.storage.external.handler` to be recognized by the repository. Here is an example for the `myfield` field type: ``` services: _defaults: autowire: true autoconfigure: true public: false App\FieldType\MyField\Storage\MyFieldStorage: ~ tags: - {name: ibexa.field_type.storage.external.handler, alias: myfield} ``` The configuration requires providing the `ibexa.field_type.storage.external.handler` tag, with the `alias` attribute being the *fieldTypeIdentifier*. You also have to inject the gateway in `arguments`, [see Gateway-based storage](#gateway-based-storage). External storage configuration for basic field types is located in [`ibexa/core/src/lib/Resources/settings/fieldtype_external_storages.yml`](https://github.com/ibexa/core/blob/main/src/lib/Resources/settings/fieldtype_external_storages.yml). Using gateway-based storage requires another service implementing `Ibexa\Core\FieldType\StorageGateway` to be injected into the [external storage handler](#storing-data-externally)). ``` services: _defaults: autowire: true autoconfigure: true public: false App\FieldType\MyField\Storage\Gateway\DoctrineStorage: ~ ``` The `ibexa.api.storage_engine.legacy.connection` is of type `Doctrine\DBAL\Connection`. If your gateway still uses an implementation of `eZ\Publish\Core\Persistence\Database\DatabaseHandler` (`eZ\Publish\Core\Persistence\Doctrine\ConnectionHandler`), instead of the `ibexa.api.storage_engine.legacy.connection`, you can pass the `ibexa.api.storage_engine.legacy.dbhandler` service. Also there can be several gateways per field type (one per storage engine). In this case it's recommended to either create base implementation which each gateway can inherit or create interface which each gateway must implement and reference it instead of specific implementation when type-hinting method arguments. > **Tip: Tip** > > Gateway configuration for built-in field types is located in [`core/src/lib/Resources/settings/storage_engines/`](https://github.com/ibexa/core/tree/main/src/lib/Resources/settings/storage_engines). ## Storing field type settings externally Just like in the case of data, storing [field type settings](https://doc.ibexa.co/en/latest/content_management/field_types/type_and_value/#field-type-settings) in content item tables may prove insufficient. It's not a problem if your setting specifies, for example, the allowed number of characters in a text field. However, the field type may represent a more complex object, for example, it may consist of two or more other fields, such as the name, product code (SKU), and price, and there can be a set of default values instead of just one. Once you add validation rules for these field values, then it becomes an issue. You can overcome this obstacle: When you create a new field type, you can move field type settings to external storage. > **Note: Note** > > Another benefit of an external storage is that there can be database relations to other objects/entities, and the database itself can maintain the integrity of data. First, create a class that implements the `Ibexa\Contracts\Core\FieldType\FieldConstraintsStorage` interface. Then, register the External Storage as a service and tag it with `ibexa.field_type.external_constraints_storage`. Make sure that the alias you use matches the identifier of the new field type: ``` services: App\FieldType\Example\ExternalStorage: tags: - { name: ibexa.field_type.external_constraints_storage, alias: } ``` # Field type validation ## Validator schema The schema for validator configuration should have a similar format to the [settings schema](https://doc.ibexa.co/en/latest/content_management/field_types/type_and_value/#field-type-settings), except it has an additional level, to group settings for a certain validation mechanism: - The key on the 1st level is a string, identifying a validator - Assigned to that is an associative array (2nd level) of settings - This associative array has a string key for each setting of the validator - It's assigned to a 3rd level associative array, the setting description - This associative array should have the same format as for normal settings For example, for the `ibexa_string` type, the validator schema could be: ``` [ 'stringLength' => [ 'minStringLength' => [ 'type' => 'int', 'default' => 0, ], 'maxStringLength' => [ 'type' => 'int' 'default' => null, ] ], ]; ``` # Field type searching Fields, or a custom field type, might contain or maintain data relevant for user searches. To make the search engine aware of the data in your field type you need to implement an additional interface and register the implementation. ## `Indexable` interface The `Ibexa\Contracts\Core\FieldType\Indexable` interface defines the methods below which are required if the field type provides data relevant to search engines. ### `getIndexData(Field $field, FieldDefinition $fieldDefinition)` This method returns the actual index data for the provided `Ibexa\Contracts\Core\Persistence\Content\Field`. The index data consists of an array of `Ibexa\Contracts\Core\Search\Field` instances. They're described below in further detail. ### `getIndexDefinition()` To be able to query data properly an indexable field type also is required to return search specification. You must return an associative array of `Ibexa\Contracts\Core\Search\FieldType` instances from this method, which could look like: ``` [ 'url' => new Search\FieldType\StringField(), 'text' => new Search\FieldType\StringField(), ] ``` This example from the `Url` field type shows that the field type always returns two indexable values, both strings. They have the names `url` and `text` respectively. ### `getDefaultMatchField()` This method retrieves the name of the default field to be used for matching. As field types can index multiple fields (see [MapLocation](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/maplocationfield/index.md) field type's implementation of this interface), this method is used to define the default field for matching. Default field is typically used by the [`Field` Search Criterion](https://doc.ibexa.co/en/latest/search/criteria_reference/field_criterion/index.md). ### `getDefaultSortField()` This method gets name of the default field to be used for sorting. As field types can index multiple fields (see [MapLocation](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/maplocationfield/index.md) field type's implementation of this interface), this method is used to define default field for sorting. Default field is typically used by the [`Field` Sort Clause](https://doc.ibexa.co/en/latest/search/sort_clause_reference/field_sort_clause/index.md). ## Register `Indexable` implementations Implement `Ibexa\Contracts\Core\FieldType\Indexable` as an extra service and register this Service using the `ibexa.field_type.indexable` tag. Example from [`indexable_fieldtypes.yaml`](https://github.com/ibexa/core/blob/main/src/lib/Resources/settings/indexable_fieldtypes.yml): ``` Ibexa\Core\FieldType\Keyword\SearchField: class: Ibexa\Core\FieldType\Keyword\SearchField tags: - {name: ibexa.field_type.indexable, alias: ibexa_keyword} ``` The `alias` should be the same as field type ID. ## Search field values The search field values returned by the `getIndexData` method are simple value objects consisting of the following properties: | Property | Description | | -------- | -------------------------------------------------------------------------------------------------- | | `$name` | The name of the field | | `$value` | The value of the field | | `$type` | An `Ibexa\Contracts\Core\Search\FieldType` instance, describing the type information of the field. | ## Search field types There are many available search field types which are handled by search backend configuration. When using them, there is no need to adapt, for example, the Solr configuration in any way. You can always use custom field types, but these might require re-configuration of the search backend. For Solr this would mean adapting the `schema.xml` file. The default available search field types that can be found in the `Ibexa\Contracts\Core\Search\FieldType` namespace are: | Field type | Description | | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `BooleanField` | Boolean values. | | `CustomField` | Custom field, for custom search data types. Probably requires additional configuration in the search backend. | | `DateField` | Date field. Can be used for date range queries. | | `DocumentField` | Document field | | `FloatField` | Field for floating point numbers. | | `FullTextField` | Represents full text searchable value of the field which can be indexed by the legacy search engine. Some full text fields are stored as an array of strings. | | `GeoLocationField` | Field used for Geo location. | | `IdentifierField` | Field used for IDs. Basically acts like the string field, but it's not queried by full-text searches | | `IntegerField` | Field for integer numbers. | | `MultipleBooleanField` | Multiple boolean values. | | `MultipleIdentifierField` | Multiple IDs values. | | `MultipleIntegerField` | Multiple integer numbers. | | `MultipleStringField` | Multiple string values. | | `PriceField` | Field for price values. Currency conversion might be applied by the search backends. Might require careful configuration. | | `StringField` | Standard string values. It's also queried by full text searches. | | `TextField` | Standard text values. It's queried by full text searches. Configured text normalizations in the search backend apply. | ## Configuring Solr As mentioned before, if you use the standard type definitions, there is no need to configure the search backend in any way. The field definitions are handled using `dynamicField` definitions in Solr, for example. If you want to configure the handling of your field, you can always add a special field definition to the Solr `schema.xml`. For fields, the field type names used by the Solr search backend look like this: `//_`. You can define custom `dynamicField` definitions to match, for example, on your custom `_` definition. You could also define a custom field definition for certain fields, like for the name field in an article: ``` ``` > **Note: Note** > > If you want to learn more about the Solr implementation and detailed information about configuring it, check out the [Solr Search Bundle](https://doc.ibexa.co/en/latest/search/search_engines/solr_search_engine/solr_overview/index.md). # Create custom generic field type The Generic field type is an abstract implementation of field types holding structured data for example, address. You can use it as a base for custom field types. The Generic field type comes with the implementation of basic methods, reduces the number of classes which must be created, and simplifies the tagging process. A more in-depth, step-by-step tutorial can be viewed here: [Creating a Point 2D field type](https://doc.ibexa.co/en/latest/tutorials/generic_field_type/creating_a_point2d_field_type/index.md). > **Tip: Tip** > > You should not use the Generic field type when you need a very specific implementation or complete control over the way data is stored. > **Caution: Simple hash values** > > A simple hash value always means an array of scalar values and/or nested arrays of scalar values. To avoid issues with format conversion, don't use objects inside the simple hash values. ## Define value object First, create `Value.php` in the `src/FieldType/HelloWorld` directory. The Value class of a field type contains only the basic logic of a field type, the rest of it's handled by the `Type` class. For more information about field type Value, see [Value handling](https://doc.ibexa.co/en/latest/content_management/field_types/type_and_value/#value-handling). The `HelloWorld` Value class should contain: - public properties that retrieve `name` - an implementation of the `__toString()` method ``` name; } public function setName(?string $name): void { $this->name = $name; } public function __toString(): string { return "Hello {$this->name}!"; } } ``` ## Define fields and configuration Next, implement a definition of a field type extending the Generic field type in the `src/FieldType/HelloWorld/Type.php` class. It provides settings for the field type and an implementation of the `Ibexa\Contracts\Core\FieldType\FieldType` abstract class. ``` add('name', TextType::class); } public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'data_class' => Value::class, ]); } } ``` Now you can map field definitions into Symfony forms with FormMapper. Add the `mapFieldValueForm()` method required by `FieldValueFormMapperInterface` and the required `use` statements to `src/FieldType/HelloWorld/Type.php`: ``` getFieldDefinition(); $fieldForm->add('value', HelloWorldType::class, [ 'required' => $definition->isRequired, 'label' => $definition->getName(), ]); } } ``` For more information about the FormMappers, see [field type form and template](https://doc.ibexa.co/en/latest/content_management/field_types/form_and_template/index.md). Next, add the `ibexa.admin_ui.field_type.form.mapper.value` tag to the service definition: ``` services: App\FieldType\HelloWorld\Type: public: true tags: - { name: ibexa.field_type, alias: hello_world } - { name: ibexa.admin_ui.field_type.form.mapper.value, fieldType: hello_world } ``` ## Render fields ### Create a template Create a template for the new field type. It defines the default rendering of the `HelloWorld` field. In the `templates/themes/standard/field_types` directory create a `field_type.html.twig` file: ``` {% block hello_world_field %} Hello {{ field.value.getName() }}! {% endblock %} ``` ### Template mapping Provide the template mapping under the `ibexa.system..field_templates` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa: system: default: field_templates: - { template: '@ibexadesign/field_types/field_type.html.twig', priority: 0 } ``` ## Final results Finally, you should be able to add a new content type in the back office interface. Navigate to **Content types** tab and under **Content** category create a new content type: *[Image: Creating new content type]* Next, define a **Hello World** field: *[Image: Defining Hello World]* After saving, your **Hello World** content type should be available under **Content** in the sidebar menu. *[Image: Creating Hello World]* # Create custom field type comparison In the back office, you can compare the contents of fields. Comparing is possible only between two versions of the same field that are in the same language. You can add the possibility to compare custom and other unsupported field types. > **Note: Note** > > The following task uses the [custom "Hello World" field type](https://doc.ibexa.co/en/latest/content_management/field_types/create_custom_generic_field_type/index.md). The configuration is based on the comparison mechanism created for the `ibexa_string` field type. ## Create Comparable class First, create a `Comparable.php` class in `src/FieldType/HelloWorld/Comparison`. This class implements the `Ibexa\Contracts\VersionComparison\FieldType\Comparable` interface with the `getDataToCompare()` method: ``` new StringComparisonValue([ 'value' => $value->getName(), ]), ]); } } ``` The `getDataToCompare()` fetches the data to compare and determines which [comparison engines](#create-comparison-engine) should be used. Register this class as a service: ``` services: App\FieldType\HelloWorld\Comparison\Comparable: tags: - { name: ibexa.field_type.comparable, alias: hello_world } ``` ## Create comparison value Next, create a `src/FieldType/HelloWorld/Comparison/Value.php` file that holds the comparison value: ``` stringValueComparisonEngine->compareValues($comparisonDataA->name, $comparisonDataB->name) ); } /** * @param \App\FieldType\HelloWorld\Comparison\Value $comparisonDataA * @param \App\FieldType\HelloWorld\Comparison\Value $comparisonDataB */ public function shouldRunComparison(FieldTypeComparisonValue $comparisonDataA, FieldTypeComparisonValue $comparisonDataB): bool { return $comparisonDataA->name->value !== $comparisonDataB->name->value; } } ``` Register the comparison engine as a service: ``` services: App\FieldType\HelloWorld\Comparison\HelloWorldComparisonEngine: tags: - { name: ibexa.field_type.comparable.engine, supported_type: App\FieldType\HelloWorld\Comparison\Value } ``` ## Add comparison result Next, create a comparison result class in `src/FieldType/HelloWorld/Comparison/HelloWorldComparisonResult.php`. ``` stringDiff; } public function isChanged(): bool { return $this->stringDiff->isChanged(); } } ``` ## Provide templates Finally, create a template for the new comparison view in `templates/themes/admin/field_types/field_type_comparison.html.twig`: ``` {% extends '@ibexadesign/version_comparison/comparison_result_blocks.html.twig' %} {% block hello_world_field_comparison %} {% apply spaceless %} {% with { 'comparison_result': comparison_result.getHelloWorldDiff() } %} {{ block('string_diff_render') }} {% endwith %} {% endapply %} {% endblock %} ``` Add configuration for this template under the `ibexa.system..field_comparison_templates` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa: system: default: field_comparison_templates: - { template: '@ibexadesign/field_types/field_type_comparison.html.twig', priority: 10 } ``` # Customize field type metadata When creating a content type definition, you add fields and configure their metadata, for example, whether they're required or translatable. If needed, you can customize that some of those options are disabled in the back office for specific field types. To do this, add custom service definition for `ModifyFieldDefinitionsCollectionTypeExtension`. For example, this configuration means that no Image field can be set as required in the definition of a content type: ``` services: ibexa.field_type_identifier.form.type_extension.modify_field_definitions_for_field_type_identifier_field_type: class: 'Ibexa\AdminUi\Form\Type\Extension\ModifyFieldDefinitionsCollectionTypeExtension' arguments: $fieldTypeIdentifier: 'ibexa_image' $modifiedOptions: disable_required_field: true tags: - form.type_extension ``` `fieldTypeIdentifier` refers to the identifier of the field type, in this case `ibexa_image`. `modifiedOptions` lists the changes you want to make. The following options are available: - `disable_identifier_field` - disables changing the field identifier - `disable_required_field` - disables setting the field as required - `disable_translatable_field` - disables setting the field as translatable - `disable_remove` - disables removing the field from content type definition (after it has been saved) *[Image: Image field with disabled required option]* # Field type reference A field type is the underlying building block of the content model. It consists of two entities: field value and field definition. Field value is determined by values entered into the content field. Field definition is provided by the content type, and holds any user defined rules used by field type to determine how a field value is, for example, validated, stored, retrieved, or formatted. Ibexa DXP comes with a collection of field types that can be used to build powerful and complex content structures. In addition, it's possible to extend the system by creating custom types for special needs. > **Tip: Tip** > > For general field type documentation, see [field type](https://doc.ibexa.co/en/latest/content_management/field_types/field_types/index.md). Custom field types have to be programmed in PHP. However, the built-in field types are usually enough for typical scenarios. The following table gives an overview of the supported field types that come with Ibexa DXP. ## Available field types | Field type | Description | Searchable in Legacy Storage engine | Searchable with Solr/Elasticsearch | | --------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- | | [Address](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/addressfield/index.md) | Stores an address. | No | No | | [Author](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/authorfield/index.md) | Stores a list of authors, each consisting of author name and author email. | No | Yes | | [BinaryFile](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/binaryfilefield/index.md) | Stores a file. | Yes | Yes | | [Checkbox](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/checkboxfield/index.md) | Stores a boolean value. | Yes | Yes | | [Content query](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/contentqueryfield/index.md) | Maps an executable repository query to a field. | No | No | | [Country](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/countryfield/index.md) | Stores country names as a string. | Yes[1](#1-note-on-legacy-search-engine) | Yes | | [Customer group](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/customergroupfield/index.md) | Stores customer group to which a user belongs. | Yes, in [Search](https://doc.ibexa.co/en/latest/search/criteria_reference/customergroupid_criterion/index.md) and [Price Search](https://doc.ibexa.co/en/latest/search/criteria_reference/price_customergroup_criterion/index.md) | Yes | | [DateAndTime](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/dateandtimefield/index.md) | Stores a full date including time information. | Yes | Yes | | [Date](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/datefield/index.md) | Stores date information. | Yes | Yes | | [EmailAddress](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/emailaddressfield/index.md) | Validates and stores an email address. | Yes | Yes | | [Float](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/floatfield/index.md) | Validates and stores a floating-point number. | No | Yes | | [Form](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/formfield/index.md) | Stores a form. | No | Yes | | [Image](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/imagefield/index.md) | Validates and stores an image. | No | Yes | | [ImageAsset](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/imageassetfield/index.md) | Stores images in independent content items of a generic Image content type. | No | Yes | | [Integer](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/integerfield/index.md) | Validates and stores an integer value. | Yes | Yes | | [ISBN](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/isbnfield/index.md) | Handles International Standard Book Number (ISBN) in 10-digit or 13-digit format. | Yes | Yes | | [Keyword](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/keywordfield/index.md) | Stores keywords. | Yes[1](#1-note-on-legacy-search-engine) | Yes | | [MapLocation](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/maplocationfield/index.md) | Stores map coordinates. | Yes, with [`MapLocationDistance` Criterion](https://doc.ibexa.co/en/latest/search/criteria_reference/maplocationdistance_criterion/index.md) | Yes | | [Matrix](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/matrixfield/index.md) | Represents and handles a table of rows and columns of data. | No | No | | [Measurement](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/measurementfield/index.md) | Validates and stores a unit of measure, and either a single measurement value, or a pair of range values. | Yes | Yes | | [Media](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/mediafield/index.md) | Validates and stores a media file. | No | Yes | | [Null](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/nullfield/index.md) | Used as fallback for missing field types and for testing purposes. | N/A | N/A | | [Page](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/pagefield/index.md) | Stores a Page with a layout consisting of multiple zones. | N/A | N/A | | [ProductSpecification](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/productspecificationfield/index.md) | Stores product attributes and VAT | Yes but only with [Product Search](https://doc.ibexa.co/en/latest/search/criteria_reference/product_search_criteria/index.md) | Yes | | [Relation](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/relationfield/index.md) | Validates and stores a relation to a content item. | Yes, with both [`Field`](https://doc.ibexa.co/en/latest/search/criteria_reference/field_criterion/index.md) and [`FieldRelation`](https://doc.ibexa.co/en/latest/search/criteria_reference/fieldrelation_criterion/index.md) Criteria | Yes | | [RelationList](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/relationlistfield/index.md) | Validates and stores a list of relations to content items. | Yes, with [`FieldRelation` Criterion](https://doc.ibexa.co/en/latest/search/criteria_reference/fieldrelation_criterion/index.md) | Yes | | [RichText](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/richtextfield/index.md) | Validates and stores structured rich text in XML. | Yes[1](#1-note-on-legacy-search-engine) | Yes | | [Selection](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/selectionfield/index.md) | Validates and stores a single selection or multiple choices from a list of options. | Yes[1](#1-note-on-legacy-search-engine) | Yes | | [TaxonomyEntry](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/taxonomyentryfield/index.md) | Stores information about the Taxonomy tree. | No | Yes | | [TaxonomyEntryAssignment](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/taxonomyentryassignmentfield/index.md) | Makes content taggable by Taxonomy. | No | Yes | | [TextBlock](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/textblockfield/index.md) | Validates and stores a larger block of text. | Yes[1](#1-note-on-legacy-search-engine) | Yes | | [TextLine](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/textlinefield/index.md) | Validates and stores a single line of text. | Yes | Yes | | [Time](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/timefield/index.md) | Stores time information. | Yes | Yes | | [Url](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/urlfield/index.md) | Stores a URL / address. | No | Yes | | [User](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/userfield/index.md) | Validates and stores information about a user. | No | No | **[1] Note on Legacy Search Engine** Legacy Search/Storage Engine index is limited to 255 characters in database design, so formatted and unformatted text blocks only index the first part. In case of multiple selection field types like, for example, Keyword, Selection, or Country, only the first choices are indexed. they're indexed only as a text blob separated by string separator. Proper indexing of these field types is done with [Solr Search engine](https://doc.ibexa.co/en/latest/search/search_engines/solr_search_engine/solr_overview/index.md). # Address field type Editions: Experience This field represents and handles address fields. It allows you to customize address fields per country. | Name | Internal name | Expected input | | --------- | --------------- | --------------------------- | | `Address` | `ibexa_address` | `string`, `string`, `array` | The Address field type is available via the Address Bundle provided by the `ibexa/fieldtype-address` package. ## PHP API field type ### Inputs: | Type | Description | Example | | -------- | --------------------------------------------- | ----------------- | | `string` | Name of the address. | `My home address` | | `string` | Country code in ISO 3166-1 alpha-2 format. | `PL` | | `array` | Additional fields, defined by address format. | see below | ### Example input ``` new FieldType\Value( 'My home address', 'PL', [ 'city' => 'Warsaw', 'region' => 'Masovian', 'postal_code' => '11-123', ] ); ``` ### Validation This field type validates whether `Country` and `Name` fields have been filled out. ### Value object #### Properties | Property | Type | Description | | ---------- | -------- | --------------------------------------------- | | `$name` | `string` | Name of the address. | | `$country` | `string` | Country code in ISO 3166-1 alpha-2 format. | | `$fields` | `array` | Additional fields, defined by address format. | #### Constructor See above (Example input). ### Formats The following default configuration defines default fields for `personal` address type: ``` formats: personal: country: default: - region - locality - street - postal_code ``` #### Modifying field configuration ``` formats: billing_address: country: DE: - tax_number - city - address - postal_code ``` Adds (or alters) an address format for `DE` country of `billing_address` type. ### Field form types By default, each field is a simple text input with a label made of field identifier. To change the type of field, you need to listen to a specific event. For each field below events are dispatched (in order): ``` ibexa.address.field.{FIELD_IDENTIFIER} ibexa.address.field.{FIELD_IDENTIFIER}.{ADDRESS_TYPE} ibexa.address.field.{FIELD_IDENTIFIER}.{ADDRESS_TYPE}.{COUNTRY_CODE} ``` #### Example ``` ibexa.address.field.tax_number ibexa.address.field.tax_number.billing_address ibexa.address.field.tax_number.billing_address.DE ``` #### Example event listener An event listener can also provide validation by using either one of [constraints provided by Symfony](https://symfony.com/doc/7.4/validation.html#supported-constraints), or a custom constraint. ``` use Ibexa\Contracts\FieldTypeAddress\Event\MapFieldEvent; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Form\Extension\Core\Type\IntegerType; use Symfony\Component\Validator\Constraints\Positive; class ExampleAddressSubscriber implements EventSubscriberInterface { public static function getSubscribedEvents(): array { return [ 'ibexa.address.field.tax_number.billing_address' => 'onBillingAddressTaxNumber', ]; } public function onBillingAddressTaxNumber(MapFieldEvent $event): void { $event->setLabel('VAT'); $event->setType(IntegerType::class); $event->setOptions([ 'attr' => ['class' => 'some-tax-number'], 'constraints' => [new Positive()], ]); } } ``` # Author field type This field type allows the storage and retrieval of one or more authors. For each author, it can handle a name and an email address. It's typically used to store information about additional authors who have written/created different parts of a content item. | Name | Internal name | Expected input | Output | | -------- | -------------- | -------------- | -------- | | `Author` | `ibexa_author` | mixed | `string` | ## PHP API field type ### Value object ##### Properties | Attribute | Type | Description | Example | | --------- | --------------------------------------- | ---------------- | --------- | | `authors` | `\Ibexa\Core\FieldType\Author\Author[]` | List of authors. | See below | Example: ``` $authorList = Author\Value([ new Author\Author([ 'id' => 1, 'name' => 'Boba Fett', 'email' => 'boba.fett@example.com' ]), new Author\Author([ 'id' => 2, 'name' => 'Darth Vader', 'email' => 'darth.vader@example.com' ]), ]); ``` ### Hash format The hash format mostly matches the value object. It has the following key `authors`. Example ``` [ [ 'id' => 1, 'name' => 'Boba Fett', 'email' => 'boba.fett@example.com' ], [ 'id' => 2, 'name' => 'Darth Vader', 'email' => 'darth.vader@example.com' ] ] ``` ##### String representation The string contains all the authors with their names and emails. Example: `John Doe john@doe.com` ### Validation This field type doesn't perform any special validation of the input value. ### Settings The Field definition of this field type can be configured with a single option: | Name | Type | Default value | Description | | --------------- | ------- | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | `defaultAuthor` | `mixed` | `Type::DEFAULT_VALUE_EMPTY` | One of the `DEFAULT_*` constants, used by the administration interface for setting the default Field value. See below for more details. | Following `defaultAuthor` default value options are available as constants in the `Ibexa\Core\FieldType\Author\Type` class: | Constant | Description | | ---------------------- | ----------------------------------------- | | `DEFAULT_VALUE_EMPTY` | Default value is empty. | | `DEFAULT_CURRENT_USER` | Default value uses currently logged user. | ``` // Author field type example settings use Ibexa\Core\FieldType\Author\Type; $settings = [ "defaultAuthor" => Type::DEFAULT_VALUE_EMPTY ]; ``` # BinaryFile field type This field type represents and handles a single binary file. It also counts the number of times the file has been downloaded from the `content/download` module. It's capable of handling virtually any file type and is typically used for storing legacy document types, for example, PDF files, Word documents, or spreadsheets. The maximum allowed file size is determined by the "Max file size" class attribute edit parameter and the `upload_max_filesize` directive in the main PHP configuration file (`php.ini`). | Name | Internal name | Expected input | Output | | ------------ | ------------------ | -------------- | ------ | | `BinaryFile` | `ibexa_binaryfile` | mixed | mixed | ## PHP API field type ### Value object #### Properties Both `BinaryFile` and `Media` Value and Type inherit from the `BinaryBase` abstract field type, and share common properties. `Ibexa\Core\FieldType\BinaryFile\Value` offers the following properties: | Attribute | Type | Description | Example | | --------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | | `id` | string | Binary file identifier. This ID depends on the [IO Handler](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/clustering/clustering/#dfs-io-handler) that is being used. With the native, default handlers (FileSystem and Legacy), the ID is the file path, relative to the binary file storage root dir (`var//storage/original` by default). | application/63cd472dd7.pdf | | `fileName` | string | The human-readable file name, as exposed to the outside. Used when sending the file for download to name the file. | 20130116_whitepaper.pdf | | `fileSize` | int | File size, in bytes. | 1077923 | | `mimeType` | string | The file's MIME type. | application/pdf | | `uri` | string | The binary file's `content/download` URI. If the URI doesn't include a host or protocol, it applies to the request domain. | /content/download/210/2707 | | `downloadCount` | integer | Number of times the file was downloaded | 0 | | `inputUri` | string | Path to a local file when creating a field value, `null` when reading a field value | `path/to/document.pdf` | #### Constructor's hash format The hash format mostly matches the value object. It has the following keys: | Key | Status | Type | Description | | --------------- | ---------- | ------- | ---------------------------------------------------------------------------------------- | | `inputUri` | mandatory | string | Path to the local file to be uploaded into the field. | | `id` | deprecated | string | Backward compatibility alias for `inputUri`. | | `path` | deprecated | string | Backward compatibility alias for `inputUri`. | | `fileName` | optional | string | Name of the file when downloaded. If not given, the basename of `inputUri` is used | | `fileSize` | optional | integer | Size of the file in bytes. If not given, the size of the `inputUri` target file is used. | | `downloadCount` | optional | integer | Number of times the file was downloaded. If not given, set to `0` (zero). | | `mimeType` | ignored | | | | `uri` | ignored | | | Example: ``` $fileContentCreateStruct->setField('file', new Ibexa\Core\FieldType\BinaryFile\Value([ 'fileName' => 'example.pdf', 'inputUri' => '/tmp/example_for_website.pdf', ])); ``` The original local file name `example_for_website.pdf` is forgotten. When downloaded, the filename is `example.pdf`. To use a remote file, you have to download it locally first, then remove it after it's used in `ContentService::createContent`. ## REST API specifics Used in the REST API, a BinaryFile field mostly serializes the hash described above. However there are a couple specifics worth mentioning. ### Reading content: `url` property When reading the contents of a field of this type, an extra key is added: `url`. This key gives you the absolute file URL, protocol and host included. Example: `http://example.com/var/ezdemo_site/storage/original/application/63cd472dd7819da7b75e8e2fee507c68.pdf` ### Creating content: `data` property When creating BinaryFile content with the REST API, it's possible to provide data as a base64 encoded string, by using the `data` fieldValue key: ``` file eng-GB My file.pdf 17589 ``` # Checkbox field type The Checkbox field type stores the current status for a checkbox input, checked or unchecked, by storing a boolean value. | Name | Internal name | Expected input type | | ---------- | --------------- | ------------------- | | `Checkbox` | `ibexa_boolean` | `boolean` | ## PHP API field type ### Value object ##### Properties The Value class of this field type contains the following properties: | Property | Type | Default value | Description | | -------- | --------- | ------------- | ------------------------------------------------------------------------------ | | `$bool` | `boolean` | `false` | This property is used for the checkbox status, represented by a boolean value. | ``` //Value object content examples use Ibexa\Core\FieldType\Checkbox\Type; // Instantiates a checkbox value with a default state (false) $checkboxValue = new Checkbox\Value(); // Checked $value->bool = true; // Unchecked $value->bool = false; ``` ##### Constructor The `Checkbox\Value` constructor accepts a boolean value: ``` // Constructor example use Ibexa\Core\FieldType\Checkbox\Type; // Instantiates a checkbox value with a checked state $checkboxValue = new Checkbox\Value( true ); ``` ##### String representation As this field type isn't a string but a boolean, it returns "1" (true) or "0" (false) in cases where it's cast to string, and it's never considered empty. # Content query field type This field type maps an executable repository query to a field. | Name | Internal name | Expected input | | ------- | --------------------- | -------------- | | `Query` | `ibexa_content_query` | `string` | The Content query field type is available via the Query field type Bundle provided by the [fieldtype-query](https://github.com/ibexa/fieldtype-query) package. For information about the field type's usage, see [Content queries](https://doc.ibexa.co/en/latest/templating/queries_and_controllers/content_queries/#content-query-field). # Country field type This field type represents one or multiple countries. | Name | Internal name | Expected input | | --------- | --------------- | -------------- | | `Country` | `ibexa_country` | `array` | ## PHP API field type ### Input expectations Example array: ``` [ "JP" => [ "Name" => "Japan", "Alpha2" => "JP", "Alpha3" => "JPN", "IDC" => 81 ] ]; ``` When you set an array directly on a content field you don't need to provide all this information, the field type assumes it's a hash and in this case accepts a simplified structure described below under [Hash format](#hash-format). ### Validation This field type validates whether multiple countries are allowed by the field definition, and whether the [Alpha2](https://www.iso.org/iso-3166-country-codes.html) is valid according to the countries configured in Ibexa DXP. ### Settings The field definition of this field type can be configured with one option: | Name | Type | Default value | Description | | ------------ | --------- | ------------- | ------------------------------------------------------------------------------------------ | | `isMultiple` | `boolean` | `false` | This setting allows (if true) or prohibits (if false) the selection of multiple countries. | ``` // Country FieldType example settings $settings = [ "isMultiple" => true ]; ``` ### Hash format The format used for serialization is simpler than the full format. It's also available when setting value on the content field, by setting the value to an array instead of the value object. Example of that shown below: ``` // Value object content example $content->fields["countries"] = [ "JP", "NO" ]; ``` The format used by the toHash method is the Alpha2 value, however the input is capable of accepting either Name, Alpha2, or Alpha3 value as shown below in the value object section. ### Value object ##### Properties The Value class of this field type contains the following properties: | Property | Type | Description | | ------------ | --------- | ------------------------------------------------------------------------------------- | | `$countries` | `array[]` | This property is used for the country selection provided as input, as its attributes. | ``` // Value object content example $value->countries = [ "JP" => [ "Name" => "Japan", "Alpha2" => "JP", "Alpha3" => "JPN", "IDC" => 81 ] ]; ``` ##### Constructor The `Country\Value` constructor initializes a new value object with the value provided. It expects an array as input. ``` // Constructor example // Instantiates a Country Value object $countryValue = new Country\Value( [ "JP" => [ "Name" => "Japan", "Alpha2" => "JP", "Alpha3" => "JPN", "IDC" => 81 ] ] ); ``` # Customer group field This field type represents a customer group that a user belongs to. | Name | Internal name | Expected input type | | ---------------- | ---------------------- | ------------------- | | `Customer group` | `ibexa_customer_group` | `int` or null | ## PHP API field type ### Value object ##### Properties The Value class of this field type contains the following properties: | Property | Type | Description | | -------- | ----- | ------------------------- | | `$id` | `int` | ID of the customer group. | # DateAndTime field type This field type represents a full date and time information. | Name | Internal name | Expected input type | | ------------- | ---------------- | ------------------- | | `DateAndTime` | `ibexa_datetime` | mixed | ## PHP API field type ### Input expectations If input value is of type `string` or `integer`, it's passed directly to the [PHP's built-in `\DateTime` class constructor](https://www.php.net/manual/en/datetime.construct.php), therefore the same input format expectations apply. It's also possible to directly pass an instance of `\DateTime`. | Type | Example | | ----------- | ---------------------------------- | | `integer` | `"2017-08-28 12:20 Europe/Berlin"` | | `integer` | `1346149200` | | `\DateTime` | `new \DateTime()` | ### Value object ##### Properties The Value class of this field type contains the following properties: | Property | Type | Description | | -------- | ----------- | ------------------------------------------------------ | | `$value` | `\DateTime` | The date and time value as an instance of `\DateTime`. | ##### Constructor The constructor for this value object initializes a new value object with the value provided. It accepts an instance of PHP's built-in `\DateTime` class. ##### String representation String representation of the date value generates the date string in the format `D Y-d-m H:i:s` as accepted by [PHP's built-in `date()` function](https://www.php.net/manual/en/function.date.php). | Character | Description | Example | | --------- | ------------------------------------------------------------------- | ------- | | D | Three letter representation of a day, range Mon to Sun | Wed | | Y | Four digit representation of a year | 2016 | | d | Two digit representation of a day, range 01 to 31 | 22 | | m | Two digit representation of a month, range 01 to 12 | 05 | | H | Two digit representation of an hour, 24-hour format, range 00 to 23 | 12 | | i | Two digit representation of minutes, range 00 to 59 | 19 | | s | Two digit representation of seconds, range 00 to 59 | 18 | Example: `Wed 2016-22-05 12:19:18` ### Hash format Hash value of this field type is an array with two keys: | Key | Type | Description | Example | | ----------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------- | | `timestamp` | `integer` | Time information in [Unix format timestamp](https://en.wikipedia.org/wiki/Unix_time). | `1400856992` | | `rfc850` | `string` | Time information as a string in [RFC 850 date format](https://datatracker.ietf.org/doc/html/rfc850). As input, this has precedence over the timestamp value. | `"Friday, 23-May-14 14:56:14 GMT+0000"` | ``` $hash = [ "timestamp" => 1400856992, "rfc850" => "Friday, 23-May-14 14:56:14 GMT+0000" ]; ``` ### Validation This field type doesn't perform any special validation of the input value. ### Settings The field definition of this field type can be configured with several options: | Name | Type | Default value | Description | | -------------- | --------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | `useSeconds` | `boolean` | `false` | Used to control displaying of seconds in the output. | | `defaultType` | `mixed` | `Type::DEFAULT_EMPTY` | One of the `DEFAULT_*` constants, used by the administration interface for setting the default field value. See below for more details. | | `dateInterval` | \`null | \\DateInterval\` | `null` | Following `defaultType` default value options are available as constants in the `Ibexa\Core\FieldType\DateAndTime\Type` class: | Constant | Description | | ------------------------------- | -------------------------------------------------------------------------------------------- | | `DEFAULT_EMPTY` | Default value is empty. | | `DEFAULT_CURRENT_DATE` | Default value uses current date. | | `DEFAULT_CURRENT_DATE_ADJUSTED` | Default value uses current date, adjusted by the interval defined in `dateInterval` setting. | ``` // DateAndTime FieldType example settings use Ibexa\Core\FieldType\DateAndTime\Type; $settings = [ "useSeconds" => false, "defaultType" => Type::DEFAULT_EMPTY, "dateInterval" => null ]; ``` ## Template rendering The template called by the [`ibexa_render_field()` Twig function](https://doc.ibexa.co/en/latest/templating/twig_function_reference/field_twig_functions/#ibexa_render_field) while rendering a Date field has access to the following parameters: | Parameter | Type | Default | Description | | --------- | -------- | ------- | --------------------------------------------------------------------------------------------------------------------------------- | | `locale` | `string` | n/a | Internal parameter set by the system based on current request locale or if not set calculated based on the language of the field. | Example: ``` {{ ibexa_render_field(content, 'datetime') }} ``` # Date field type This field type represents a date without time information. | Name | Internal name | Expected input type | | ------ | ------------- | ------------------- | | `Date` | `ibexa_date` | mixed | #### PHP API field type ### Input expectations If input value is in `string` or `integer` format, it's passed directly to [PHP's built-in `\DateTime` class constructor](https://www.php.net/manual/en/datetime.construct.php), therefore the same input format expectations apply. It's also possible to directly pass an instance of `\DateTime`. | Type | Example | | ----------- | ---------------------------------- | | `string` | `"2012-08-28 12:20 Europe/Berlin"` | | `integer` | `1346149200` | | `\DateTime` | `new \DateTime()` | Time information is **not stored**. Before storing, the provided input value is set to the beginning of the day in the given or the environment timezone. ### Value object ##### Properties The Value class of this field type contains the following properties: | Property | Type | Description | | -------- | ----------- | ------------------------------------------- | | `$date` | `\DateTime` | This property is used for the text content. | ##### String representation String representation of the date value generates the date string in the format "l d F Y" as accepted by [PHP's built-in `date()` function](https://www.php.net/manual/en/function.date.php). | Character | Description | Example | | --------- | ------------------------------------------------------------------- | --------- | | l | Textual representation of a day of the week, range Monday to Sunday | Wednesday | | d | Two digit representation of a day, range 01 to 31 | 22 | | F | Textual representation of a month, range January to December | May | | Y | Four digit representation of a year | 2016 | Example: `Wednesday 22 May 2016` ##### Constructor The constructor for this value object initializes a new value object with the value provided. It accepts an instance of [PHP's built-in `\DateTime` class](https://www.php.net/manual/en/datetime.construct.php). ### Hash format Hash value of this field type is an array with two keys: | Key | Type | Description | Example | | ----------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | | `timestamp` | `integer` | Time information in [unix format timestamp](https://en.wikipedia.org/wiki/Unix_time). | `1400856992` | | `rfc850` | `string` | Time information as a string in [RFC 850 date format](https://datatracker.ietf.org/doc/html/rfc850). As input, this has higher precedence over the timestamp value. | `"Friday, 23-May-14 14:56:14 GMT+0000"` | ``` // Example of the hash value in PHP $hash = [ "timestamp" => 1400856992, "rfc850" => "Friday, 23-May-14 14:56:14 GMT+0000" ]; ``` ### Validation This field type doesn't perform any special validation of the input value. ### Settings The field definition of this field type can be configured with a single option: | Name | Type | Default value | Description | | ------------- | ------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | `defaultType` | `mixed` | `Type::DEFAULT_EMPTY` | One of the `DEFAULT_*` constants, used by the administration interface for setting the default field value. See below for more details. | Following `defaultType` default value options are available as constants in the `Ibexa\Core\FieldType\Date\Type` class: | Constant | Description | | ---------------------- | -------------------------------- | | `DEFAULT_EMPTY` | Default value is empty. | | `DEFAULT_CURRENT_DATE` | Default value uses current date. | ``` // Date field type example settings use Ibexa\Core\FieldType\Date\Type; $settings = [ "defaultType" => Type::DEFAULT_EMPTY ]; ``` ## Template rendering The template called by [the `ibexa_render_field()` Twig function](https://doc.ibexa.co/en/latest/templating/twig_function_reference/field_twig_functions/#ibexa_render_field) while rendering a Date field has access to the following parameters: | Parameter | Type | Description | | --------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------- | | `locale` | `string` | Internal parameter set by the system based on current request locale or if not set, calculated based on the language of the field. | Example: ``` {{ ibexa_render_field(content, 'date') }} ``` # EmailAddress field type The EmailAddress field type represents an email address, in the form of a string. | Name | Internal name | Expected input type | | -------------- | ------------- | ------------------- | | `EmailAddress` | `ibexa_email` | `string` | ## PHP API field type ### Value object ##### Properties The `Value` class of this field type contains the following properties: | Property | Type | Description | | -------- | -------- | --------------------------------------------------------------------- | | `$email` | `string` | This property is used for the input string provided as email address. | ``` // Value object content example use Ibexa\Core\FieldType\EmailAddress\Type; // Instantiates an EmailAddress Value object with default value (empty string) $emailaddressValue = new Type\Value(); // Email definition $emailaddressValue->email = "someuser@example.com"; ``` ##### Constructor The `EmailAddress\Value` constructor initializes a new value object with the value provided. It accepts a string as input. ``` // Constructor example use Ibexa\Core\FieldType\EmailAddress\Type; // Instantiates an EmailAddress Value object $emailaddressValue = new Type\Value( "someuser@example.com" ); ``` ##### String representation String representation of the field type's value object is the email address contained in it. Example: `someuser@example.com` ### Hash format Hash value for this field type's Value is simply the email address as a string. Example: `someuser@example.com` ### Validation This field type uses the `EmailAddressValidator` validator as a resource which tests the string supplied as input against a pattern, to make sure that a valid email address has been provided. If the validations fail, a `ValidationError` is thrown, specifying the error message. ### Settings This field type doesn't support settings. # Float field type This field type stores numeric values which are provided as floats. | Name | Internal name | Expected input | | ------- | ------------- | -------------- | | `Float` | `ibexa_float` | `float` | ## PHP API field type ### Input expectations The field type expects a number as input. Both decimal and integer numbers are accepted. | Type | Example | | ------- | ------------ | | `float` | `194079.572` | | `int` | `144` | ### Value object ##### Properties The Value class of this field type contains the following properties: | Property | Type | Description | | -------- | ------- | ------------------------------------------------------------- | | `$value` | `float` | This property is used to store the value provided as a float. | ``` // Value object content example use Ibexa\Core\FieldType\Float\Type; // Instantiates a Float Value object $floatValue = new Type\Value(); $float->value = 284.773 ``` ##### Constructor The `Float\Value` constructor initializes a new value object with the value provided. It expects a numeric value with or without decimals. ``` // Constructor example use Ibexa\Core\FieldType\Float\Type; // Instantiates a Float Value object $floatValue = new Type\Value( 284.773 ); ``` ### Validation This field type supports `FloatValueValidator`, defining maximum and minimum float value: | Name | Type | Default value | Description | | --------------- | ------- | ------------- | --------------------------------------------------------------------------------- | | `minFloatValue` | `float` | \`null | This setting defines the minimum value this field type which is allowed as input. | | `maxFloatValue` | `float` | \`null | This setting defines the maximum value this field type which is allowed as input. | ``` // Validator configuration example in PHP use Ibexa\Core\FieldType\Float\Type; $contentTypeService = $repository->getContentTypeService(); $floatFieldCreateStruct = $contentTypeService->newFieldDefinitionCreateStruct( "float", "ibexa_float" ); // Accept only numbers between 0.1 and 203.99 $floatFieldCreateStruct->validatorConfiguration = [ "FileSizeValidator" => [ "minFloatValue" => 0.1, "maxFloatValue" => 203.99 ] ]; ``` ### Settings This field type doesn't support settings. # Form field type Editions: Experience The Form field type stores a Form consisting of one or more form fields. | Name | Internal name | | ------ | ------------- | | `Form` | `ibexa_form` | For more information about working with Forms, see [Forms](https://doc.ibexa.co/en/latest/content_management/forms/work_with_forms/index.md). # Image field type The Image field type allows you to store an image file. | Name | Internal name | | ------- | ------------- | | `Image` | `ibexa_image` | A **variation service** handles the conversion of the original image into different formats and sizes through a set of preconfigured named variations, for example, large, small, medium, or black and white thumbnail. ## PHP API field type ### Value object The `value` property of an Image field returns an `Ibexa\Core\FieldType\Image\Value` object with the following properties: ##### Properties | Property | Type | Example | Description | | ----------------- | ------ | ---------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | string | `0/8/4/1/1480-1-eng-GB/image.png` | The image's unique identifier. Usually the path, or a part of the path. To get the full path, use the `uri` property. | | `alternativeText` | string | `Picture of an apple.` | The alternative text, as entered in the field's properties. This property is optional. It's recommended that you require the alternative text for an image when you add the Image field to a content type, by selecting the "Alternative text is required" checkbox. | | `fileName` | string | `image.png` | The original image's filename, without the path. | | `fileSize` | int | `37931` | The original image's size, in bytes. | | `uri` | string | `var/ezdemo_site/storage/images/0/8/4/1/1480-1-eng-GB/image.png` | The original image's URI. | | `imageId` | string | `240-1480` | A special image ID, used by REST. | | `inputUri` | string | `var/storage/images/test/199-2-eng-GB/image.png` | Input image file URI. | | `width` | int | `960` | Original image width in pixels. | | `height` | int | `540` | Original image height in pixels. | ### Settings This field type doesn't support settings. ### Image variations Using the variation Service, variations of the original image can be obtained. They're `Ibexa\Contracts\Core\Variation\Values\ImageVariation` objects with the following properties: | Property | Type | Example | Description | | -------------- | -------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ | | `width` | int | `200` | The variation's width in pixels. | | `height` | int | `112` | The variation's height in pixels. | | `name` | string | `medium` | The variation's identifier, name of the image variation. | | `info` | mixed | n/a | Extra information about the image, depending on the image type, such as EXIF data. If there is no information, the `info` value is `null`. | | `fileSize` | int | `31010` | Size (in byte) of current variation. | | `mimeType` | string | `image/png` | The MIME type. | | `fileName` | string | `my_image.png` | The name of the file. | | `dirPath` | string | `var/storage/images/test/199-2-eng-GB` | The path to the file. | | `uri` | string | `var/storage/images/test/199-2-eng-GB/apple.png` | The variation's URI. Complete path with a name of image file. | | `lastModified` | DateTime | `"2017-08-282 12:20 Europe/Berlin"` | When the variation was last modified. | ### Field Definition options The Image field type supports one `FieldDefinition` option: the maximum size for the file. > **Note: Note** > > Maximum size is 10MB. We recommend setting the `upload_max_filesize` key in the `php.ini` configuration file to a value equal to or higher than that. It prevents validation errors while editing content types. ## Using an Image field To read more about handling images and image variations, see the [Images documentation](https://doc.ibexa.co/en/latest/content_management/images/images/index.md). ### Template Rendering When displayed using `ibexa_render_field`, an Image field outputs this type of HTML: ``` Alternative text ``` The template called by the [`ibexa_render_field()` Twig function](https://doc.ibexa.co/en/latest/templating/twig_function_reference/field_twig_functions/#ibexa_render_field) while rendering a Image field accepts the following parameters: | Parameter | Type | Default | Description | | --------- | -------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- | | `alias` | `string` | `"original"` | The image variation name, must be defined in your SiteAccess's `image_variations` settings. Defaults to "original", the originally uploaded image. | | `width` | `int` | n/a | Optionally to specify a different width set on the image HTML tag then one from image variation. | | `height` | `int` | n/a | Optionally to specify a different height set on the image HTML tag then one from image variation. | | `class` | `string` | n/a | Optionally to specify a specific html class for use in custom JavaScript and/or CSS. | Example: ``` {{ ibexa_render_field( content, 'image', { 'parameters':{ 'alias': 'imagelarge', 'width': 400, 'height': 400 } } ) }} ``` The raw field can also be used if needed. Image variations for the field's content can be obtained using the `ibexa_image_alias` Twig helper: ``` {% set imageAlias = ibexa_image_alias( field, versionInfo, 'medium' ) %} ``` The variation's properties can be used to generate the required output: ``` {{ field.value.alternativeText }} ``` ### With the REST API Image Fields within REST are exposed by the `application/vnd.ibexa.api.Content` media-type. An Image field looks like this: ``` 1480 image eng-GB /var/ezdemo_site/storage/images/0/8/4/1/1480-1-eng-GB/kidding.png kidding.png 37931 240-1480 /var/ezdemo_site/storage/images/0/8/4/1/1480-1-eng-GB/kidding.png /api/ibexa/v2/content/binary/images/240-1480/variations/articleimage /api/ibexa/v2/content/binary/images/240-1480/variations/articlethumbnail ``` Children of the `fieldValue` node list the general properties of the field's original image (for example, `fileSize`, `fileName`, or `inputUri`), and its variations. For each variation, a URI is provided. Requested through REST, this resource generates the variation if it doesn't exist yet, and list the variation details: ``` /var/ezdemo_site/storage/images/0/8/4/1/1480-1-eng-GB/kidding_tiny.png image/png 30 30 1361 ``` ### From PHP code #### Getting an image variation The variation service, `ibexa.field_type.ibexa_image.variation_service`, can be used to generate/get variations for a field. It expects a VersionInfo, the Image field, and the variation name as a string (`large`, `medium`, and more.): ``` $variation = $imageVariationHandler->getVariation( $imageField, $versionInfo, 'large' ); echo $variation->uri; ``` ## Manipulating image content ### From PHP As for any field type, there are several ways to input content to a field. For an Image, the quickest is to call `setField()` on the ContentStruct: ``` $createStruct = $contentService->newContentCreateStruct( $contentTypeService->loadContentType( 'image' ), 'eng-GB' ); $createStruct->setField( 'image', '/tmp/image.png' ); ``` To customize the Image's alternative texts, you must first get an `Image\Value` object, and set this property. For that, you can use the `Image\Value::fromString()` method that accepts the path to a local file: ``` $createStruct = $contentService->newContentCreateStruct( $contentTypeService->loadContentType( 'image' ), 'eng-GB' ); $imageField = \Ibexa\Core\FieldType\Image\Value::fromString( '/tmp/image.png' ); $imageField->alternativeText = 'My alternative text'; $createStruct->setField( 'image', $imageField ); ``` You can also provide a hash of `Image\Value` properties, either to `setField()`, or to the constructor: ``` $imageValue = new \Ibexa\Core\FieldType\Image\Value( [ 'id' => '/tmp/image.png', 'fileSize' => 37931, 'fileName' => 'image.png', 'alternativeText' => 'My alternative text' ] ); $createStruct->setField( 'image', $imageValue ); ``` ### From REST The REST API expects field values to be provided in a hash-like structure. Those keys are identical to those expected by the `Image\Value` constructor: `fileName`, `alternativeText`. In addition, image data can be provided using the `data` property, with the image's content encoded as base64. #### Creating an Image field ``` 247 image eng-GB rest-rocks.jpg HTTP ``` ### Updating an Image field Updating an Image field requires that you re-send existing data. This can be done by re-using the field obtained via REST, **removing the variations key**, and updating `alternativeText`, `fileName` or `data`. If you don't want to change the image itself, don't provide the `data` key. ``` 247 image eng-GB media/images/507-1-eng-GB/Existing-image.png Updated alternative text Updated-filename.png ``` ## Naming Each storage engine determines how image files are named. ### Legacy Storage Engine naming Images are stored within the following directory structure: `///////--/` With the following values: - `VarDir` = `var` (default) - `StorageDir` = `storage` (default) - `ImagesStorageDir` = `images` (default) - `FieldId` = `1480` - `VersionNumber` = `1` - `LanguageCode` = `eng-GB` Images are stored in `web/var/ezdemo_site/storage/images/0/8/4/1/1480-1-eng-GB`. Using the field ID digits in reverse order as the folder structure maximizes sharding of files through multiple folders on the filesystem. Within this folder, images are named like the uploaded file, suffixed with an underscore, and the variation name: - `MyImage.png` - `MyImage_large.png` - `MyImage_rss.png` # ImageAsset field type Image Asset field type enables storing images in independent content items of a generic Image content type, in the media library. It makes them reusable across system. | Name | Internal name | | ------------ | ------------------- | | `ImageAsset` | `ibexa_image_asset` | ### Input expectations Example array: | Type | Description | Example | | ------------------------------------------------------------ | ----------------------------------------------- | ---------- | | `Ibexa\Core\FieldType\ImageAsset\Value` | Image Asset field type value object. | See below. | | `Ibexa\Contracts\Core\Repository\Values\Content\ContentInfo` | ContentInfo instance of the Asset content item. | n/a | | `string` | ID of the Asset content item. | `"150"` | | `integer` | ID of the Asset content item. | `150` | ### Value object ##### Properties Value object of `ibexa_image_asset` contains the following properties: | Property | Type | Description | | ---------------------- | -------- | ---------------------------------------------------------------- | | `destinationContentId` | `int` | Related content ID. | | `alternativeText` | `string` | The alternative image text (for example "Picture of an apple."). | ``` // Value object content example $imageAssetValue->destinationContentId = $contentInfo->id; $imageAssetValue->alternativeText = "Picture of an apple."; ``` ##### Constructor The `ImageAsset\Value` constructor initializes a new value object with the value provided. It expects an ID of a content item representing asset and the alternative text. ``` // Constructor example // Instantiates a ImageAsset Value object $imageAssetValue = new ImageAsset\Value($contentInfo->id, "Picture of an apple."); ``` ### Validation This field type validates if: - `destinationContentId` points to a content item which has correct content type ### Configuration ImageAsset field type allows configuring the following options: | Name | Description | Default value | | -------------------------- | -------------------------------------- | ------------- | | `content_type_identifier` | Content type used to store assets. | `image` | | `content_field_identifier` | Field identifier used for asset data. | `image` | | `name_field_identifier` | Field identifier used for asset name. | `name` | | `parent_location_id` | Location where the assets are created. | `51` | Example configuration: ``` ibexa: system: default: fieldtypes: ibexa_image_asset: content_type_identifier: photo content_field_identifier: image name_field_identifier: title parent_location_id: 106 ``` ## Customizing ImageAsset field type rendering Internally, the Image Asset Type is rendered via subrequest (similar to other relation types). Rendering customization is possible by configuring view type `asset_image`: ``` ibexa: system: default: content_view: asset_image: default: template: ::custom_image_asset_template.html.twig match: [] ``` ## Generating image variation from the Image Asset Thanks to the `Ibexa\Bundle\Core\Imagine\ImageAsset` decorator you can work with `Ibexa\Contracts\Core\Variation` in the same way as with [Image field type](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/imagefield/index.md). # Integer field type This field type represents an integer value. | Name | Internal name | Expected input | | --------- | --------------- | -------------- | | `Integer` | `ibexa_integer` | `integer` | ## PHP API field type ### Input expectations | Type | Example | | --------- | ------- | | `integer` | `2397` | ### Value object ##### Properties The Value class of this field type contains the following properties: | Property | Type | Description | | -------- | ----- | ---------------------------------------------------------------- | | `$value` | `int` | This property is used to store the value provided as an integer. | ``` // Value object content example $integer->value = 8 ``` ##### Constructor The `Integer\Value` constructor initializes a new value object with the value provided. It expects a numeric, integer value. ``` // Constructor example use Ibexa\Core\FieldType\Integer; // Instantiates a Integer Value object $integerValue = new Integer\Value( 8 ); ``` ### Hash format Hash value of this field type is an integer value as a string. Example: `"8"` ### String representation String representation of the field type's value returns the integer value as a string. Example: `"8"` ### Validation This field type supports `IntegerValueValidator`, defining maximum and minimum float value: | Name | Type | Default value | Description | | ----------------- | ----- | ------------- | --------------------------------------------------------------------------------- | | `minIntegerValue` | `int` | `0` | This setting defines the minimum value this field type which is allowed as input. | | `maxIntegerValue` | `int` | `null` | This setting defines the maximum value this field type which is allowed as input. | ``` // Example of validator configuration in PHP $validatorConfiguration = [ "minIntegerValue" => 1, "maxIntegerValue" => 24 ]; ``` ### Settings This field type doesn't support settings. # ISBN field type This field type represents an ISBN string either an ISBN-10 or ISBN-13 format. | Name | Internal name | Expected input type | | ------ | ------------- | ------------------- | | `ISBN` | `ibexa_isbn` | `string` | ## PHP API field type ### Value object ##### Properties The Value class of this field type contains the following properties: | Property | Type | Description | | -------- | -------- | ------------------------------------------ | | `$isbn` | `string` | This property is used for the ISBN string. | ##### String representation An ISBN's string representation is the `$isbn` property's value, as a string. ##### Constructor The constructor for this value object initializes a new value object with the value provided. It accepts a string as argument and sets it to the `isbn` attribute. ### Validation The input passed into this field type is subject of ISBN validation depending on the field settings in its FieldDefinition stored in the content type. An example of this field setting is shown below and controls if input is validated as ISBN-13 or ISBN-10: ``` Array ( [isISBN13] => true ) ``` # Keyword field type This field type stores one or several comma-separated keywords as a string or array of strings. | Name | Internal name | Expected input | | --------- | --------------- | -------------- | | `Keyword` | `ibexa_keyword` | \`string[] | ## PHP API field type ### Input expectations | Type | Example | | ---------- | --------------------------------------------------------- | | `string` | `"documentation"` | | `string` | `"php, Ibexa Platform, html5"` | | `string[]` | `[ "Ibexa", "Enterprise", "User Experience Management" ]` | ### Value object ##### Properties The Value class of this field type contains the following properties: | Property | Type | Description | | -------- | ---------- | -------------------------------------- | | `$value` | `string[]` | Holds an array of keywords as strings. | ``` // Value object content example use Ibexa\Core\FieldType\Keyword\Value; // Instantiates a Value object $keywordValue = new Value(); // Sets an array of keywords as a value $keyword->value = [ "php", "css3", "html5", "Ibexa Platform" ]; ``` #### Constructor The `Keyword\Value` constructor initializes a new value object with the value provided. It expects a list of keywords, either comma-separated in a string or as an array of strings. ``` // Constructor example use Ibexa\Core\FieldType\Keyword\Value; // Instantiates a Value object with an array of keywords $keywordValue = new Value( [ "php5", "css3", "html5" ] ); // Instantiates a Value object with a list of keywords in a string // This is equivalent to the example above $keywordValue = new Value( "php5,css3,html5" ); ``` # MapLocation field type This field type represents a geographical location. As input it expects three values: - two float values latitude and longitude, - a string value, corresponding to the name or address of the location. | Name | Internal name | Expected input | | ------------- | --------------------- | -------------- | | `MapLocation` | `ibexa_gmap_location` | `mixed` | ## PHP API field type ### Input expectations | Type | Example | | ------- | ------------------------------------------------------------------------------------- | | `array` | `[ 'latitude' => 59.928732, 'longitude' => 10.777888, 'address' => "Ibexa Nordics" ]` | ### Value object ##### Properties The Value class of this field type contains the following properties: | Property | Type | Description | | ------------ | -------- | ----------------------------------------------------------------------- | | `$latitude` | `float` | This property stores the latitude value of the map location reference. | | `$longitude` | `float` | This property stores the longitude value of the map location reference. | | `$address` | `string` | This property stores the address of map location. | ##### Constructor The `MapLocation\Value` constructor initializes a new value object with values provided as hash. Accepted keys are `latitude` (`float`), `longitude` (`float`), `address` (`string`). ``` // Constructor example // Instantiates a MapLocation Value object $MapLocationValue = new MapLocation\Value( [ 'latitude' => 59.928732, 'longitude' => 10.777888, 'address' => "Ibexa Nordics" ] ); ``` ## Template rendering The template called by [the `ibexa_render_field()` Twig function](https://doc.ibexa.co/en/latest/templating/twig_function_reference/field_twig_functions/#ibexa_render_field) while rendering a Map location field accepts the following parameters: | Parameter | Type | Default | Description | | ------------- | ---------- | ------- | -------------------------------------------------------------------------------------------------------------- | | `draggable` | `boolean` | `true` | Whether to enable a draggable map. | | `height` | \`string | false\` | `"200px"` | | `scrollWheel` | `boolean` | `true` | Allows you to disable scroll wheel starting to zoom when mouse comes over the map as user scrolls down a page. | | `showInfo` | `booolean` | `true` | Whether to show a latitude, longitude and the address outside of the map. | | `showMap` | `boolean` | `true` | Whether to show the OpenStreetMap. | | `width` | \`string | false\` | `"500px"` | | `zoom` | `integer` | `13` | The initial zoom level on the map. | Example: ``` {{ ibexa_render_field(content, 'location', {'parameters': {'width': '100%', 'height': '330px', 'showMap': true, 'showInfo': false}}) }} ``` > **Note: Note** > > The option to automatically get user coordinates through the "Locate me" button is only available when the back office is served through the `https://` protocol. # Matrix field type This field represents and handles a table of rows and columns of data. | Name | Internal name | Expected input | | -------- | -------------- | -------------- | | `Matrix` | `ibexa_matrix` | `array` | The Matrix field type is available via the Matrix Bundle provided by the [ibexa/fieldtype-matrix](https://github.com/ibexa/fieldtype-matrix) package. ## PHP API field type ### Input expectations | Type | Description | Example | | ------- | -------------------------------------------------------------------------------------- | --------- | | `array` | array of `Ibexa\FieldTypeMatrix\FieldType\Value\Row` objects which contain column data | see below | Example of input: ``` new FieldType\Value([ new FieldType\Value\Row(['col1' => 'Row 1, Col 1', 'col2' => 'Row 1, Col 2']), new FieldType\Value\Row(['col1' => 'Row 2, Col 1', 'col2' => 'Row 2, Col 2']), new FieldType\Value\Row(['col1' => 'Row 3, Col 1', 'col2' => 'Row 3, Col 2']), ]); ``` ### Value object `Ibexa\FieldTypeMatrix\FieldType\Value` offers the following properties: | Property | Type | Description | | -------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------- | | `rows` | `RowsCollection` | Array of `Row` objects containing an array of cells (`Row::getCells()` returns array `['col1' => 'Value 1', /* ... */]`). | ### Validation The minimum number of rows is set on content type level for each field. Validation checks for empty rows. A row is considered empty if it contains only empty cells (or cells containing only spaces). Empty rows are removed. If, after removing empty rows, the number of rows doesn't fulfill the configured `Minimum number of rows`, the field doesn't validate. For example, the following input doesn't validate if `Minimum number of rows` is set to 3, because the second row is empty: ``` new FieldType\Value([ new FieldType\Value\Row(['col1' => 'Row 1, Col 1', 'col2' => 'Row 1, Col 2']), new FieldType\Value\Row(['col1' => '', 'col2' => '']), new FieldType\Value\Row(['col1' => 'Row 3, Col 1', 'col2' => 'Row 3, Col 2']), ]); ``` ## GraphQL field type operations To get a field of the Matrix field type with GraphQL, you need to specify a content ID, a content type, and a field type. The types that are returned are named after the Type and the field: - `{TypeIdentifier}{FieldIdentifier}Row` The example below shows a GraphQL query for a Recipe content item (belonging to a content type with a Matrix field added), that has two fields: - `name`: `ibexa_string` - `ingredients`: `ibexa_matrix` with two columns: `ingredient` and `quantity` ``` { content { recipe(id: 123) { name ingredients { ingredient quantity } } } } ``` The Type returned for the Matrix field exposes columns defined in the field definition: ``` { "data": { "content": { "recipe": { "name": "Cake ingredients", "ingredients": [ { "ingredient": "Butter", "quantity": "200 grams" }, { "ingredient": "Sugar", "quantity": "100 grams" } ] } } } } ``` ### Query for the field type and field definition's details With this query you can inspect details of specific content type. In case of a Matrix field, you can ask for the list of columns, their names, and identifiers. ``` { content { _types { recipe { ingredients { settings { minimumRows columns { name identifier } } } } } } } ``` The response lists the exposed field type settings: - minimumRows - columns - name - identifier Example response: ``` { "data": { "content": { "_types": { "recipe": { "ingredients": { "settings": { "minimumRows": 1, "columns": [ { "name": "ingredient", "identifier": "ingredient" }, { "name": "quantity", "identifier": "quantity" } ] } } } } } } } ``` ### Mutation To create a Matrix field type you need to define field type and field definition identifiers. The types that are used for input are named after the Type and the field: - `{TypeIdentifier}{FieldIdentifier}RowInput`, for example, `dish.nutritionFacts`, `event.agenda`: `DishNutritionFactsRowInput`, `EventAgendaRowInput` The example below shows how to create a Recipe content item (belonging to a content type with a Matrix field type added) that has two fields: - `name`: `"Cake Ingredient List"` - `ingredients`: `ibexa_matrix` with two columns: `ingredient` and `quantity` ``` mutation AddRecipe { createRecipe( language: eng_GB parentLocationId: 2, input: { name: "Cake Ingredient List", ingredients: [ {ingredient: "sugar", quantity: "100 grams"} {ingredient: "butter", quantity: "200 grams"} ] } ) { name } } ``` The response confirms creation of the new Recipe field: ``` { "data": { "createRecipe": { "name": "Cake Ingredient List" } } } ``` # Measurement field type The Measurement field type represents measurement information. It stores the unit of measure, and either a single measurement value, or a pair of top and bottom values that defines a range. | Name | Internal name | Expected input type | | ------------- | ------------------- | -------------------------------------------------- | | `Measurement` | `ibexa_measurement` | `Ibexa\Contracts\Measurement\Value\ValueInterface` | ## PHP API field type ### Input expectations To create a value, you use a service that implements `Ibexa\Contracts\Measurement\MeasurementServiceInterface`. You must inject the service directly with [dependency injection](https://doc.ibexa.co/en/latest/api/php_api/php_api/#service-container). The service contains the following API endpoints: - `buildSimpleValue` that is used to handle a single value - `buildRangeValue` that is used to handle a range Assuming that the service exists as `$measurementService`, the expected input examples are as follows: | Type | Example | | --------------------------------------------------------- | -------------------------------------------------------------------- | | `\Ibexa\Contracts\Measurement\Value\SimpleValueInterface` | `$measurementService->buildSimpleValue('length', 2.5, 'centimeter')` | | `\Ibexa\Contracts\Measurement\Value\RangeValueInterface` | `$measurementService->buildRangeValue('length', 1.2, 4.5, 'inch')` | ### Value object ##### Properties The Value class of this field type contains the following properties: | Property | Type | Description | | -------- | -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `$value` | `Ibexa\Contracts\Measurement\Value\ValueInterface` | Stores the Measurement API Value, which can be either an instance of `Ibexa\Contracts\Measurement\Value\SimpleValueInterface` or `Ibexa\Contracts\Measurement\Value\RangeValueInterface`. | ##### Constructor The `Measurement\Value` constructor for this value object initializes a new value object with the value provided. As its first argument it accepts an object of `Ibexa\Contracts\Measurement\Value\ValueInterface` type. Depending on the selected input type, the object resembles the following examples: ``` // Simple input (single value) example // @var MeasurementServiceInterface $measurementService // Instantiates a Measurement Value object $measurementValue = new Measurement\Value( $measurementService->buildSimpleValue( 'length', 13.5, 'centimeter' ) ); ``` ``` // Range input value example // @var MeasurementServiceInterface $measurementService // Instantiates a Measurement Value object $measurementValue = new Measurement\Value( $measurementService->buildRangeValue( 'volume', 0.5, 0.7, 'liter' ) ); ``` ### Validation The Measurement field type validates measurement types and units passed within the value object against a list of the ones that the system supports, which can be found in the `vendor/ibexa/measurement/src/bundle/Resources/config/builtin_units.yaml` file. ### Modify and add Measurement types and units You can extend the default list of Measurement types and units by modifying the existing entries or adding new ones. To do this, you modify the YAML configuration. To override an existing designation of the unit of measure by changing the symbol that corresponds to a nautical unit of speed, and to add a rotational speed unit, add the following lines to your [YAML configuration](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa_measurement: types: speed: knot: { symbol: kt } revolutions per minute: { symbol: RPM } ibexa: system: default: measurement: types: speed: - revolutions per minute ``` To add a new Measurement type with its own new units, add the following lines to your YAML configuration: ``` ibexa_measurement: types: my_type: my_unit: { symbol: my, is_base_unit: true } ibexa: system: default: measurement: types: my_type: - my_unit ``` The configuration also requires that exactly one unit needs to be marked as `is_base_unit` as in highlighted line above. > **Note: Note** > > To be available for selection in the back office, each new Measurement type or unit must be enabled for the back office SiteAccess. Next, you need to define how the new unit should be converted under the `ibexa.system..ibexa_measurement` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa_measurement: conversion: formulas: - { source_unit: foo, target_unit: bar, formula: 'value / 100' } types: length: foo: { symbol: foo } bar: { symbol: bar } ``` > **Tip: Tip** > > The `target_unit` must be an existing unit, for example meter, otherwise the conversion results in an error. ## Template rendering The Measurement field is rendered with the [`ibexa_render_field()`](https://doc.ibexa.co/en/latest/templating/twig_function_reference/field_twig_functions/#ibexa_render_field) Twig function. Example: ``` {{ ibexa_render_field(content, 'measurement') }} ``` # Media field type This field type represents and handles a media (audio/video) binary file. It's capable of handling the following types of files: - Apple QuickTime - Adobe Flash - Microsoft Windows Media - Real Media - Silverlight - HTML5 Video - HTML5 Audio | Name | Internal name | Expected input | | ------- | ------------- | -------------- | | `Media` | `ibexa_media` | mixed | ## PHP API field type ### Input expectations | Type | Description | Example | | ---------------------------------- | ---------------------------------------------------------------------------------------- | ----------------------------- | | `string` | Path to the media file. | `/Users/jane/butterflies.mp4` | | `Ibexa\Core\FieldType\Media\Value` | Media field type value object with path to the media file as the value of `id` property. | See below. | ### Value object ##### Properties `Ibexa\Core\FieldType\Media\Value` offers the following properties. Both `Media` and `BinaryFile` Value and Type inherit from the `BinaryBase` abstract field type and share common properties. | Property | Type | Description | Example | | --------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------- | | `id` | string | Media file identifier. This ID depends on the [IO Handler](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/clustering/clustering/#dfs-io-handler) that is being used. With the native, default handlers (FileSystem and Legacy), the ID is the file path, relative to the binary file storage root dir (`var//storage/original` by default). | application/63cd472dd7819da7b75e8e2fee507c68.mp4 | | `fileName` | string | The human-readable file name, as exposed to the outside. Used to name the file when sending it for download. | butterflies.mp4 | | `fileSize` | int | File size, in bytes. | 1077923 | | `mimeType` | string | The file's MIME type. | video/mp4 | | `uri` | string | The binary file's HTTP URI. If the URI doesn't include a host or protocol, it applies to the request domain. **The URI is not publicly readable, and must NOT be used to link to the file for download.** Use `ibexa_render_field` to generate a valid link to the download controller. | /var/ezdemo_site/storage/original/application/63cd472dd7819da7b75e8e2fee507c68.mp4 | | `hasController` | boolean | Whether the media has a controller when being displayed. | true | | `autoplay` | boolean | Whether the media should be automatically played. | true | | `loop` | boolean | Whether the media should be played in a loop. | false | | `height` | int | Height of the media. | 300 | | `width` | int | Width of the media. | 400 | | `path` | string | **deprecated** | | ### Hash format The hash format mostly matches the value object. It has the following keys: - `id` - `path` (for backwards compatibility) - `fileName` - `fileSize` - `mimeType` - `uri` - `hasController` - `autoplay` - `loop` - `height` - `width` ### Validation The field type supports `FileSizeValidator`, defining maximum size of media file in bytes: | Name | Type | Default value | Description | | ------------- | ----- | ------------- | ---------------------------------- | | `maxFileSize` | `int` | `false` | Maximum size of the file in bytes. | ``` // Example of using Media field type validator in PHP use Ibexa\Core\FieldType\Media\Type; $contentTypeService = $repository->getContentTypeService(); $mediaFieldCreateStruct = $contentTypeService->newFieldDefinitionCreateStruct( "media", "ibexa_media" ); // Setting maximum file size to 5 megabytes $mediaFieldCreateStruct->validatorConfiguration = [ "FileSizeValidator" => [ "maxFileSize" => 5 * 1024 * 1024 ] ]; ``` ### Settings The field type supports the `mediaType` setting, defining how the media file should be handled in output. | Name | Type | Default value | Description | | ----------- | ----- | ------------------------ | ----------------------------------------------------------- | | `mediaType` | mixed | `Type::TYPE_HTML5_VIDEO` | Type of the media, accepts one of the predefined constants. | List of all available `mediaType` constants is defined in the `Ibexa\Core\FieldType\Media\Type` class: | Name | Description | | ------------------- | ----------------------- | | `TYPE_FLASH` | Adobe Flash | | `TYPE_QUICKTIME` | Apple QuickTime | | `TYPE_REALPLAYER` | Real Media | | `TYPE_SILVERLIGHT` | Silverlight | | `TYPE_WINDOWSMEDIA` | Microsoft Windows Media | | `TYPE_HTML5_VIDEO` | HTML5 Video | | `TYPE_HTML5_AUDIO` | HTML5 Audio | ``` // Example of using Media field type settings in PHP use Ibexa\Core\FieldType\Media\Type; $contentTypeService = $repository->getContentTypeService(); $mediaFieldCreateStruct = $contentTypeService->newFieldDefinitionCreateStruct( "media", "ibexa_media" ); // Setting Adobe Flash as the media type $mediaFieldCreateStruct->fieldSettings = [ "mediaType" => Type::TYPE_FLASH, ]; ``` # Null field type This field type is used as fallback for migration scenarios, and for testing purposes. | Name | Internal name | Expected input type | | ------ | ------------- | ------------------- | | `Null` | (variable) | mixed | ## Description The Null field type aids when migrating from eZ Publish Platform and earlier legacy versions. It's a dummy for legacy field types that aren't implemented in Ibexa DXP. Null field type accepts anything provided as a value and is usually combined with: - NullConverter: Makes it not store anything to the legacy storage engine (database), nor it reads any data. - Unindexed: Indexable class making sure nothing is indexed to configured search engine. This field type doesn't have its own fixed internal name. Its identifier is instead configured as needed by passing it as an argument to the constructor. ### Example for usage of Null field type The following example shows how an `example` field type could be configured as a Null field type: ``` # Null Fieldtype example configuration services: ibexa.field_type.example: class: Ibexa\Core\FieldType\Null\Type arguments: [example] tags: [{name: ibexa.field_type, alias: example}] ibexa.field_type.example.converter: class: Ibexa\Core\Persistence\Legacy\Content\FieldValue\Converter\NullConverter tags: [{name: ibexa.field_type.storage.legacy.converter, alias: example}] ibexa.field_type.example.indexable: class: Ibexa\Core\FieldType\Unindexed tags: [{name: ibexa.field_type.indexable, alias: example}] ``` # Page field type Editions: Experience Page field type represents a page with a layout consisting of multiple zones. Each zone can in turn contain blocks. Page field type is only used in the page content type that is included in Ibexa Experience. | Name | Internal name | Expected input | | ------------- | -------------------- | --------------- | | `LandingPage` | `ibexa_landing_page` | `string` (JSON) | > **Caution: Page Builder** > > If you create content type with both `ibexa_landing_page` and `ibexa_user` field types, you aren't redirected to Page Builder after selecting `Edit` or `Create`. This is caused by `ibexa_user` field type which requires separate handling. You're redirected to the standard back office edit or create mode. ## Layout and zones Layout defines how a page is divided into zones. The placement of zones is defined in a template which is a part of the layout configuration. You can modify the template to define your own zone layout. For information on how to create and configure new blocks for the page, see [Page layouts](https://doc.ibexa.co/en/latest/templating/render_content/render_page/#render-a-layout). ## Blocks For information on how to create and configure new blocks for the page, see [Create custom Page block](https://doc.ibexa.co/en/latest/content_management/pages/create_custom_page_block/index.md). ## Rendering pages Page rendering takes place while editing or viewing. When rendering a page, its zones are passed to the layout as a `zones` array with a `blocks` array each. You can access them using twig (for example, `{{ zones[0].id }}` ). Each div that's a zone should have the `data-ibexa-zone-id` attribute with zone ID as a value for a zone container. To render a block inside the layout, use the Twig [`render_esi()`](https://symfony.com/doc/7.4/reference/twig_reference.html#render-esi) function to call `Ibexa\\Bundle\\FieldTypePage\\Controller\\BlockController::renderAction`. The `renderAction` has the following parameters: | Parameter | Description | | -------------- | -------------------------------------------------------------------------------- | | `locationId` | ID of the location of the content item which can be accessed by `contentInfo.id` | | `blockId` | ID of the block which you want to render. | | `versionNo` | Version number of the content item to render. | | `languageCode` | Language code of the content item to render. | If your block needs to be dependent on query parameters like "page" and you already configured your custom block with a [`cacheable_query_params configuration`](https://doc.ibexa.co/en/latest/content_management/pages/page_blocks/#block-configuration), pass [`ibexa_append_cacheable_query_params(block)`](https://doc.ibexa.co/en/latest/templating/twig_function_reference/page_twig_functions/#ibexa_append_cacheable_query_params) as the third argument to the [`controller()` Twig function](https://symfony.com/doc/7.4/reference/twig_reference.html#controller) so that the HTTP cache can vary based on those query parameters. In a fresh installation, the feature is only used by the back office's [Dashboard blocks](https://doc.ibexa.co/projects/userguide/en/5.0/getting_started/dashboard/dashboard_block_reference/): "My content" and "Review queue". Example usage: ``` {{ render_esi(controller('Ibexa\\Bundle\\FieldTypePage\\Controller\\BlockController::renderAction', { 'locationId': locationId, 'blockId': block.id, 'versionNo': versionInfo.versionNo, 'languageCode': field.languageCode }, ibexa_append_cacheable_query_params(block))) }} ``` As a whole a sample layout could look as follows: ```
    {# The required attribute for the displayed zone #}
    {# If a zone with [0] index contains any blocks #} {% if zones[0].blocks %} {# for each block #} {% for block in blocks %} {# create a new layer with appropriate ID #}
    {# render the block by using the "Ibexa\\Bundle\\FieldTypePage\\Controller\\BlockController::renderAction" controller #} {# location.id is the ID of the Location of the current content item, block.id is the ID of the current block #} {{ render_esi(controller('Ibexa\\Bundle\\FieldTypePage\\Controller\\BlockController::renderAction', { 'locationId': locationId, 'blockId': block.id, 'versionNo': versionInfo.versionNo, 'languageCode': field.languageCode }, ibexa_append_cacheable_query_params(block))) }}
    {% endfor %} {% endif %}
    ``` # Product specification field type Editions: Headless This field represents and handles [product attributes](https://doc.ibexa.co/en/latest/product_catalog/products/#product-attributes) and [VAT](https://doc.ibexa.co/en/latest/product_catalog/prices/#vat). Consider it as internal to the [product catalog](https://doc.ibexa.co/en/latest/product_catalog/product_catalog/index.md). | Name | Internal name | Expected input | | ---------------------- | ----------------------------- | -------------- | | `ProductSpecification` | `ibexa_product_specification` | mixed | > **Caution: Caution** > > The presence of a specification (`ibexa_product_specification`) field distincts product types from content types. Don't remove this field from a product type (or it becomes a unreachable hidden content type). Don't add such field to a content type (or it becomes an uneditable unusable product type). # Relation field type This field type makes it possible to store and retrieve the value of a relation to another content item. | Name | Internal name | Expected input | | ---------- | ----------------------- | -------------- | | `Relation` | `ibexa_object_relation` | mixed | ## PHP API field type ### Input expectations | Type | Example | | --------- | ------- | | `string` | `"150"` | | `integer` | `150` | ### Value object ##### Properties The Value class of this field type contains the following properties: | Property | Type | Description | | ----------------------- | -------- | ----------- | | `$destinationContentId` | \`string | int | ``` // Value object content example $relation->destinationContentId = $contentInfo->id; ``` ##### Constructor The `Relation\Value` constructor initializes a new value object with the value provided. It expects a mixed value. ``` // Constructor example // Instantiates a Relation Value object $relationValue = new Relation\Value( $contentInfo->id ); ``` ### Validation This field type validates whether the provided relation exists, but before that it checks that the value is either a string or an int. ### Settings The field definition of this field type can be configured with three options: | Name | Type | Default value | Description | | ----------------------- | -------- | --------------------------------- | ------------------------------------------------------------------------------ | | `selectionMethod` | `int` | `Relation\Type::SELECTION_BROWSE` | *This setting is not implemented yet, only one selection method is available.* | | `selectionRoot` | `string` | `null` | This setting defines the selection root. | | `selectionContentTypes` | `array` | `[]` | An array of content type IDs that are allowed for related Content. | ``` // Relation FieldType example settings use Ibexa\Core\FieldType\Relation\Type; $settings = [ "selectionMethod" => 1, "selectionRoot" => null, "selectionContentTypes" => [] ]; ``` # RelationList field type This field type makes it possible to store and retrieve values of a relation to other content items. | Name | Internal name | Expected input | | -------------- | ---------------------------- | -------------- | | `RelationList` | `ibexa_object_relation_list` | `mixed` | ## PHP API field type ### Input expectations | Type | Description | Example | | ------------------------------------------------------------ | ------------------------------------------- | ------------------------------ | | \`int | string\` | ID of the related content item | | `array` | An array of related Content IDs | `[ 24, 42 ]` | | `Ibexa\Contracts\Core\Repository\Values\Content\ContentInfo` | ContentInfo instance of the related Content | n/a | | `Ibexa\Core\FieldType\RelationList\Value` | RelationList field type value object | See below. | ### Value Object ##### Properties `Ibexa\Core\FieldType\RelationList\Value` contains the following properties: | Property | Type | Description | Example | | ----------------------- | ------- | ------------------------------- | ------------ | | `destinationContentIds` | `array` | An array of related Content IDs | `[ 24, 42 ]` | ``` // Value object content example $relationList->destinationContentId = [ $contentInfo1->id, $contentInfo2->id, 170 ]; ``` ##### Constructor The `RelationList\Value` constructor initializes a new value object with the value provided. It expects a mixed array as value. ``` //Constructor example // Instantiates a RelationList Value object $relationListValue = new RelationList\Value( [ $contentInfo1->id, $contentInfo2->id, 170 ] ); ``` ### Validation This field type validates if: - the `selectionMethod` specified is `\Ibexa\Core\FieldType\RelationList\Type::SELECTION_BROWSE` or `\Ibexa\Core\FieldType\RelationList\Type::SELECTION_DROPDOWN`. A validation error is thrown if the value doesn't match. - the `selectionDefaultLocation` specified is `null`, `string` or `integer`. If the type validation fails a validation error is thrown. - the value specified in `selectionContentTypes` is an `array`. If not, a validation error in given. - the number of content items selected in the field isn't greater than the `selectionLimit`. > **Note: Note** > > The dropdown selection method isn't implemented yet. ### Settings The field definition of this field type can be configured with the following options: | Name | Type | Default value | Description | | -------------------------- | -------- | ------------------ | ------------------------------------------------------------------ | | `selectionMethod` | `mixed` | `SELECTION_BROWSE` | Method of selection in the back-end interface. | | `selectionDefaultLocation` | \`string | integer\` | `null` | | `selectionContentTypes` | `array` | `[]` | An array of content type IDs that are allowed for related Content. | Following selection methods are available: | Name | Description | | -------------------- | --------------------------- | | `SELECTION_BROWSE` | Selection uses browse mode. | | `SELECTION_DROPDOWN` | *Not implemented yet* | ### Validators | Name | Type | Default value | Description | | -------------------------------------------- | --------- | ------------- | --------------------------------------------------------------------------------------------------------- | | `RelationListValueValidator[selectionLimit]` | `integer` | `0` | The number of content items that can be selected in the field. When set to 0, any number can be selected. | ``` // Example of using settings and validators configuration in PHP use Ibexa\Core\FieldType\RelationList\Type; $fieldSettings = [ "selectionMethod" => Type::SELECTION_BROWSE, "selectionDefaultLocation" => null, "selectionContentTypes" => [] ]; $validators = [ "RelationListValueValidator" => [ "selectionLimit" => 0, ] ]; ``` ### GraphQL integration This field type is paginating the results when queried using [GraphQL](https://doc.ibexa.co/en/latest/api/graphql/graphql/index.md). To learn more, see [Pagination in GraphQL](https://doc.ibexa.co/en/latest/api/graphql/graphql_queries/#pagination). # RichText field type The RichText field type is available via the RichText field type Bundle provided by the [ibexa/fieldtype-richtext](https://github.com/ibexa/fieldtype-richtext) package. This field type validates and stores structured rich text in [DocBook](https://docbook.org/) XML format, and exposes it in several formats. | Name | Internal name | Expected input | | ---------- | ---------------- | -------------- | | `RichText` | `ibexa_richtext` | mixed | ## PHP API field type ### Value object `Ibexa\FieldTypeRichText\FieldType\RichText\Value` offers the following properties: | Property | Type | Description | | -------- | ------------- | ------------------------------------------------------ | | `xml` | `DOMDocument` | Internal format value as an instance of `DOMDocument`. | ### Input expectations | Type | Description | | -------------------------------------------------- | -------------------------------------------------------------------------------- | | `string` | XML document in one of the field type's input formats as a string. | | `DOMDocument` | XML document in one of the field type's input formats as a `DOMDocument` object. | | `Ibexa\FieldTypeRichText\FieldType\RichText\Value` | An instance of the field type's `Value` object. | ##### Input formats The field type expects an XML value as input, in the form of a string, `DOMDocument` object, or field type's `Value` object. The field type's `Value` object must hold the value in the field type's [internal format](#internal-format). For a string of a `DOMDocument` object, if the input doesn't conform to this format, it's converted into it. ##### Internal format As its internal format, the RichText field type uses a [custom flavor of the DocBook format](#custom-docbook-format). ```
    This is a title. This is a paragraph.
    ``` ##### XHTML5 edit format The XHTML5 format is used by the Online Editor. ```

    This is a title.

    This is a paragraph.

    ``` ## Custom DocBook format > **Caution: Caution** > > The custom DocBook format described below is subject to change and isn't covered by backwards compatibility promise. You can use the Ibexa flavor of the DocBook format in PHP API and in REST API requests by providing the DocBook content as a string. The following example shows how to pass DocBook content to a [create struct](https://doc.ibexa.co/en/latest/content_management/content_api/creating_content/#creating-content-item-draft): ``` $contentCreateStruct = $contentService->newContentCreateStruct( $contentType, "eng-GB" ); $inputString = <<
    This is a title. This is a paragraph.
    DOCBOOK; $contentCreateStruct->setField( "description", $inputString ); ``` When creating RichText content with the REST API, use the `xml` key of the `fieldValue` tag: ``` <?xml version="1.0" encoding="UTF-8"?> <section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ezxhtml="http://ibexa.co/xmlns/dxp/docbook/xhtml" xmlns:ezcustom="http://ibexa.co/xmlns/dxp/docbook/custom" version="5.0-variant ezpublish-1.0"> <title ezxhtml:level="2">This is a title.</title> </section> ``` ### DocBook elements The RichText format enriches [DocBook](https://docbook.org/) with the following custom elements: - `section` - main element of a RichText field - `ezembed` - holds embedded images - `ezembedinline` - holds embedded content items - `eztemplate` - holds custom tags, including built-in custom tags for embedded Facebook, Twitter, and YouTube content - `eztemplateinline` - holds inline custom tags - `ezconfig` - contains configuration for custom tags and other elements - `ezvalue` - contains values for other elements, such as `ezconfig` or `ezembed` - `ezattribute` - contains attributes for other elements, such as `ezconfig` or `ezembed` > **Note: Unsupported DocBook elements** > > Some DocBook elements aren't supported by RichText. Refer to [`ezpublish.rng`](https://github.com/ibexa/fieldtype-richtext/blob/main/src/bundle/Resources/richtext/schemas/docbook/ezpublish.rng#L137) for a full list. ### Online Editor elements Elements of the Online Editor correspond to the following sample DocBook code blocks. #### Text formatting ``` Anchor text Center aligned Left aligned bold italic underlined subscript superscript crossed out
    This is a block quote.
    ``` #### Heading ``` My heading ``` #### Code block ``` ``` #### Unordered list ``` 1st level bullet point 1st level bullet point 2nd level bullet point 2nd level bullet point ``` #### Ordered list ``` 1st level numbered point 1st level numbered point 2nd level numbered point ``` #### Embedded content ``` ``` #### Inline embedded content ``` embed inline ``` #### Image ``` medium ``` #### Table ``` This is a merged table cell ``` #### YouTube ``` https://youtu.be/Y-1d5zdeg9A false ``` #### Twitter ``` https://twitter.com/BBCSpringwatch/status/1401622026973032452 light 500 en true ``` #### Facebook ``` https://www.facebook.com/bbcnews/posts/10158930827817217?__tn__=-R 120 ``` # Selection field type The Selection field type stores single selections or multiple choices from a list of options, by populating a hash with the list of selected values. | Name | Internal name | Expected input type | | ----------- | ----------------- | ------------------- | | `Selection` | `ibexa_selection` | mixed | ## PHP API field type ### Input expectations | Type | Example | | ------- | ---------- | | `array` | `[ 1, 2 ]` | ### Value object ##### Properties The Value class of this field type contains the following properties: | Property | Type | Description | | ------------ | ------- | ----------------------------------------------------------------------------------------------------------------- | | `$selection` | `int[]` | This property is used for the list of selections, which is a list of integer values, or one single integer value. | ``` // Value object content examples // Single selection $value->selection = 1; // Multiple selection $value->selection = [ 1, 4, 5 ]; ``` ##### Constructor The `Selection\Value` constructor accepts an array of selected element identifiers. ``` // Constructor example // Instanciates a selection value with items #1 and #2 selected $selectionValue = new Selection\Value( [ 1, 2 ] ); ``` ##### String representation String representation of this field type is its list of selections as a string, concatenated with a comma. Example: `"1,2,24,42"` ### Hash format Hash format of this field type is the same as value object's `selection` property. ``` // Example of value in hash format $hash = [ 1, 2 ]; ``` ### Validation This field type validates the input, verifying if all selected options exist in the field definition and checks if multiple selections are allowed in the field definition. If any of these validations fail, a `ValidationError` is thrown, specifying the error message. When option validation fails, a list with the invalid options is also presented. ### Settings | Name | Type | Default value | Description | | ------------ | --------- | ------------- | ------------------------------------------------------------------ | | `isMultiple` | `boolean` | `false` | Used to allow or prohibit multiple selection from the option list. | | `options` | `hash` | `[]` | Stores the list of options defined in the field definition. | ``` // Selection field type example settings use Ibexa\Core\FieldType\Selection\Type; $settings = [ "isMultiple" => true, "options" => [1 => 'One', 2 => 'Two', 3 => 'Three'] ]; ``` # TaxonomyEntry field type TaxonomyEntry is a field type that stores information about the parent entry in the taxonomy tree, placing the taxonomy entry (tag or product category) in the taxonomy structure. | Name | Internal name | Expected input | | --------------- | ---------------------- | -------------- | | `TaxonomyEntry` | `ibexa_taxonomy_entry` | `array` | ## PHP API field type ### Input expectations A `TaxonomyEntry` field accepts an array with an `Ibexa\Contracts\Taxonomy\Value\TaxonomyEntry` object. | Type | Description | Example | | ------- | -------------------------------------------------------------------------------------------------- | --------- | | `array` | array with an `Ibexa\Contracts\Taxonomy\Value\TaxonomyEntry` object under the `taxonomy_entry` key | see below | Example using an `Ibexa\Taxonomy\FieldType\TaxonomyEntry\Value` object: ``` $taxonomyEntry = $this->taxonomyService->loadEntryByIdentifier('example_entry', 'tags'); new \Ibexa\Taxonomy\FieldType\TaxonomyEntry\Value( new \Ibexa\Contracts\Taxonomy\Value\TaxonomyEntry( $taxonomyEntry ) ); ``` Example using array: ``` [ 'taxonomy_entry' => $taxonomyEntry, // load Entry using TaxonomyService ] ``` ### Value object #### Properties | Property | Type | Description | | --------------- | -------------------------------------------------- | ----------- | | `taxonomyEntry` | \`Ibexa\\Contracts\\Taxonomy\\Value\\TaxonomyEntry | null\` | #### Constructor The constructor accepts an `Ibexa\Contracts\Taxonomy\Value\TaxonomyEntry` object. ``` // Constructor example use Ibexa\Taxonomy\FieldType\TaxonomyEntry; // Fetches TaxonomyEntry from TaxonomyService $taxonomyEntry = $this->taxonomyService->loadEntryByIdentifier('example_entry', 'tags');   // Instantiates a checkbox value with a checked state $taxonomyEntryFieldTypeValue = new TaxonomyEntry\Value($taxonomyEntry); ``` #### String representation `taxonomyEntry` string identifier or empty string if no Taxonomy Entry is selected. #### Hash format An array with `taxonomy_entry` key containing `Ibexa\Contracts\Taxonomy\Value\TaxonomyEntry` object or `null`. #### Validation No validation. #### Settings The field definition of this field type can be configured with the following options: | Name | Type | Default value | Description | | ---------- | -------- | ------------- | ---------------------------------------- | | `taxonomy` | `string` | `null` | Taxonomy from which you choose an entry. | #### Template rendering The `TaxonomyEntry field` is rendered with the [`ibexa_render_field()`](https://doc.ibexa.co/en/latest/templating/twig_function_reference/field_twig_functions/#ibexa_render_field) Twig function. # TaxonomyEntryAssignment field type `TaxonomyEntryAssignment` field is used to integrate content with the Taxonomy module. It allows you to select tags or categories and assign them to content. This field type assigns tags to the content in the data action, so then you can use `TaxonomyService` on this content item. > **Caution: Duplicate taxonomy fields** > > Because tags are assigned per content item, not per field, you cannot use two **Taxonomy Entry Assignment** fields with the same taxonomy type in one content type. To be able to assign tags to the content, first, you need to add a `TaxonomyEntryAssignment` field to the content type definition. | Name | Internal name | Expected input | | ------------------------- | --------------------------------- | ------------------------------------------------ | | `TaxonomyEntryAssignment` | `ibexa_taxonomy_entry_assignment` | array with `taxonomyEntries` and `taxonomy` keys | ## PHP API field type ### Input expectations | Type | Description | Example | | ------- | ------------------------------------------------------------------------------------------------------------------------------------------- | --------- | | `array` | array with `Ibexa\Contracts\Taxonomy\Value\TaxonomyEntry` objects under `taxonomy_entries` key and Taxonomy identifier under `taxonomy` key | see below | Example using an `Ibexa\Taxonomy\FieldType\TaxonomyEntryAssignment\Value` object: ``` $taxonomyEntry1 = $this->taxonomyService->loadEntryByIdentifier('example_entry', 'tags'); $taxonomyEntry2 = $this->taxonomyService->loadEntryByIdentifier('example_entry_2', 'tags'); new \Ibexa\Taxonomy\FieldType\TaxonomyEntryAssignment\Value( [ $taxonomyEntry1, $taxonomyEntry2, // ... ], 'tags', ); ``` Example using array: ``` [ 'taxonomy_entries' => [$taxonomyEntry, $taxonomyEntry2], // load entries using TaxonomyService 'taxonomy' => 'tags', ] ``` ### Value object #### Properties | Property | Type | Description | | --------------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `taxonomyEntry` | array of `Ibexa\Contracts\Taxonomy\Value\TaxonomyEntry` | Stores selected taxonomy entry. | | `taxonomy` | `string` | Stores the taxonomy identifier, all `taxonomyEntries` have to be assigned to this taxonomy and the identifier has to match the settings of the field type in content type configuration. | #### Constructor The constructor accepts `taxonomyEntries` and `taxonomy` as described above. #### String representation If the field has no entries - empty string. If the field has entries (for example: "Cars and 5 more") - a string displaying the first taxonomy entry and the number of rest of the entries. #### Hash format An array of: - `taxonomy_entries` with numerical IDs of entries. - `taxonomy` string identifier of a taxonomy. #### Validation The field type validates if all Taxonomy Entries from the value are assigned to the configured taxonomy. #### Settings | Name | Type | Default value | Description | | ---------- | -------- | ------------- | ------------------------------------ | | `taxonomy` | `string` | `null` | Taxonomy from which entry is chosen. | #### Template rendering The `TaxonomyEntryAssignment` field is rendered with the [`ibexa_render_field()`](https://doc.ibexa.co/en/latest/templating/twig_function_reference/field_twig_functions/#ibexa_render_field) Twig function. # TextBlock field type The field type handles a block of multiple lines of unformatted text. It's capable of handling up to 16,777,216 characters. | Name | Internal name | Expected input type | | ----------- | ------------- | ------------------- | | `TextBlock` | `ibexa_text` | `string` | ## PHP API field type ### Input expectations | Type | Example | | -------- | --------------------------------------- | | `string` | `"This is a block of unformatted text"` | ### Value object ##### Properties The Value class of this field type contains the following properties: | Property | Type | Description | | -------- | -------- | ------------------------------------------- | | `$text` | `string` | This property is used for the text content. | ##### String representation A TextBlock's string representation is the `$text` property's value, as a string. ##### Constructor The constructor for this value object initializes a new value object with the value provided. It accepts a string as argument and imports it to the `$text` attribute. ### Validation This field type doesn't perform any special validation of the input value. ### Settings Settings contain only one option: | Name | Type | Default value | Description | | ---------- | --------- | ------------- | ------------------------------------------------------------- | | `textRows` | `integer` | `10` | Number of rows for the editing box in the back-end interface. | # TextLine field type This field type makes possible to store and retrieve a single line of unformatted text. It's capable of handling up to 255 characters. | Name | Internal name | Expected input type | | ---------- | -------------- | ------------------- | | `TextLine` | `ibexa_string` | `string` | ## PHP API field type ### Value object ##### Properties The Value class of this field type contains the following properties: | Property | Type | Description | | -------- | -------- | ------------------------------------------- | | `$text` | `string` | This property is used for the text content. | ##### String representation A TextLine's string representation is the `$text` property's value, as a string. ##### Constructor The constructor for this value object initializes a new value object with the value provided. It accepts a string as argument and imports it to the `$text` attribute. ### Validation The input passed into this field type is subject to validation by the `StringLengthValidator`. The length of the string provided must be between the minimum length defined in `minStringLength` and the maximum defined in `maxStringLength`. The default value for both properties is 0, which means that the validation is disabled by default. To set the validation properties, the `validateValidatorConfiguration()` method needs to be inspected, which receives an array with `minStringLength` and `maxStringLength` like in the following representation: ``` [ 'StringLengthValidator' => [ 'maxStringLength' => 60 'minStringLength' => 1 ] ] ``` # Time field type This field type represents time information. Date information is **not stored**. What is stored is the number of seconds, calculated from the beginning of the day in the given or the environment timezone. | Name | Internal name | Expected input type | | ------ | ------------- | ------------------- | | `Time` | `ibexa_time` | mixed | ## PHP API field type ### Input expectations If input value is of type `string` or `integer`, it's passed directly to the [PHP's built-in `\DateTime` class](https://www.php.net/manual/en/datetime.construct.php) constructor, therefore the same input format expectations apply. It's also possible to directly pass an instance of `\DateTime`. | Type | Example | | ----------- | ---------------------------------- | | `string` | `"2012-08-28 12:20 Europe/Berlin"` | | `integer` | `1346149200` | | `\DateTime` | `new \DateTime()` | ### Value object ##### Properties The Value class of this field type contains the following properties: | Property | Type | Description | | -------- | --------- | ----------- | | `$time` | \`integer | null\` | ##### Constructor The constructor for this value object initializes a new value object with the value provided. It accepts an integer representing the number of seconds since the beginning of the day. ##### String representation String representation of the date value generates the date string in the format "H:i:s" as accepted by [PHP's built-in `date()` function](https://www.php.net/manual/en/function.date.php). | Character | Description | Example | | --------- | ------------------------------------------------------------------- | ------- | | H | Two digit representation of an hour, 24-hour format, range 00 to 23 | 12 | | i | Two digit representation of minutes, range 00 to 59 | 14 | | s | Two digit representation of seconds, range 00 to 59 | 56 | Example: `"12:14:56"` ### Hash format Value in hash format is an integer representing a number of seconds since the beginning of the day. Example: `36000` ### Validation This field type doesn't perform validation of the input value. ### Settings The Field definition of this field type can be configured with several options: | Name | Type | Default value | Description | | ------------- | ------------------------------------------------ | --------------------- | --------------------------------------------------------------------------------- | | `useSeconds` | `boolean` | `false` | Used to control displaying of seconds in the output. | | `defaultType` | `Type::DEFAULT_EMPTY Type::DEFAULT_CURRENT_TIME` | `Type::DEFAULT_EMPTY` | The constant used here defines default input value when using back-end interface. | ``` // Time field type example settings use Ibexa\Core\FieldType\Time\Type; $settings = [ "defaultType" => DateAndTime::DEFAULT_EMPTY ]; ``` ## Template rendering The template called by [the `ibexa_render_field()` Twig function](https://doc.ibexa.co/en/latest/templating/twig_function_reference/field_twig_functions/#ibexa_render_field) while rendering a Date field has access to the following parameters: | Parameter | Type | Default | Description | | --------- | -------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------- | | `locale` | `string` | n/a | Internal parameter set by the system based on current request locale or, if not set, calculated based on the language of the field. | Example: ``` {{ ibexa_render_field(content, 'time') }} ``` # URL field type This field type makes it possible to store and retrieve a URL. It's formed by the combination of a link and the respective text. | Name | Internal name | Expected input | | ----- | ------------- | -------------- | | `Url` | `ibexa_url` | `string` | ## PHP API field type ### Input expectations | Type | Description | Example | | -------- | --------------------------------------------- | ------------------------ | | `string` | Link content provided to the value. | "" | | `string` | Text content that represents the stored link. | "Ibexa" | ### Value object ##### Properties The Value class of this field type contains the following properties: | Property | Type | Description | | -------- | -------- | ---------------------------------------------------------------------------------------------------- | | `$link` | `string` | This property stores the link provided to the value of this field type. | | `$text` | `string` | This property stores the text to represent the stored link provided to the value of this field type. | ``` // Value object content example $url->link = "https://www.ibexa.co"; $url->text = "Ibexa"; ``` ##### Constructor The `Url\Value` constructor initializes a new value object with the provided value. It expects two comma-separated strings, corresponding to the link and text. ``` // Constructor example // Instantiates an Url Value object $UrlValue = new Url\Value( "https://www.ibexa.co/", "Ibexa" ); ``` ### Hash format | Key | Type | Description | Example | | ------ | -------- | ------------- | ------------------------- | | `link` | `string` | Link content. | "" | | `text` | `string` | Text content. | "Ibexa" | ``` // Example of the hash value in PHP $hash = [ "link" => "https://www.ibexa.co/", "text" => "Ibexa" ]; ``` ### Validation This field type doesn't perform validation. But some validation can be made afterward, see [External URL validation](https://doc.ibexa.co/en/latest/content_management/url_management/url_management/#external-url-validation) for more information. ### Settings This field type doesn't have settings. # User field type This field type validates and stores information about a user. | Name | Internal name | Expected input | | ------ | ------------- | -------------- | | `User` | `ibexa_user` | ignored | ## PHP API field type ### Value object | Property | Type | Description | Example | | ------------------ | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------- | | `hasStoredLogin` | `boolean` | Denotes if user has stored login. | `true` | | `contentId` | \`int | string\` | ID of the content item corresponding to the user. | | `login` | `string` | Username. | `john` | | `email` | `string` | The user's email address. | `john@smith.com` | | `passwordHash` | `string` | Hash of the user's password. | `1234567890abcdef` | | `passwordHashType` | `mixed` | Algorithm user for generating password hash as a `PASSWORD_HASH_*` constant defined in `Ibexa\Contracts\Core\Repository\Values\User\User` class. | `User::PASSWORD_HASH_PHP_DEFAULT` | | `maxLogin` | `int` | Maximum number of concurrent logins. | `1000` | ##### Available password hash types | Constant | Description | | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------- | | `Ibexa\Contracts\Core\Repository\Values\User\User::DEFAULT_PASSWORD_HASH` | Default password hash, used when none is specified, may change over time. | | `Ibexa\Contracts\Core\Repository\Values\User\User::PASSWORD_HASH_PHP_DEFAULT` | Passwords hashed by PHP's default algorithm, which may change over time. | | `Ibexa\Contracts\Core\Repository\Values\User\User::PASSWORD_HASH_BCRYPT` | Bcrypt hash of the password. | # Collaborative editing With Collaborative editing feature multiple users can work on the same content created in Ibexa DXP simultaneously, streamlining the content creation and review process. Users can invite both internal and external collaborators to a session, giving them access for editing or previewing. Additionally, they can collaborate using a Real-time collaboration, an advanced part of the collaboration feature, to write and review content in a live mode thanks to CKEditor. Real-time collaboration syncs changes instantly and shows user avatars and colored tags to indicate who is editing specific part of the Rich Text field. This feature also introduces new dashboard tabs for managing shared drafts and joining collaboration sessions easily. ## Getting started - [Collaborative editing product guide](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/content_management/collaborative_editing/collaborative_editing_guide/): The Collaborative editing product guide provides a full description of the features and benefits that this module brings to the clients. - [Collaborative editing](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/content_management/collaborative_editing/configure_collaborative_editing/): Configure the Collaborative editing feature. - [Policies](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/permissions/policies/#collaborative-editing): Learn about the available Collaborative editing policies - [Collaborative editing](https://doc.ibexa.co/projects/userguide/en/4.6/content_management/collaborative_editing/): Learn about Collaborative editing LTS update and its capabilities. ## Development - [Collaborative editing API](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/content_management/collaborative_editing/collaborative_editing_api/): Use PHP API to manage invitations, sessions, and participants while using collaborative editing feature. - [Collaboration events](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/api/event_reference/collaboration_events/): Events that are triggered when working with collaborative editing feature. - [REST API Reference](https://doc.ibexa.co/en/4.6/api/rest_api/rest_api_reference/rest_api_reference.html#collaborative-editing): See the available endpoints for Collaborative editing - [Extend Collaborative editing](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/content_management/collaborative_editing/extend_collaborative_editing/): Extend Collaborative editing - [Collaboration Search Criterion reference](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/search/collaboration_search_reference/collaboration_criteria/): Search Criteria available for Collaboration search - [Collaboration Search Sort Clauses reference](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/search/collaboration_search_reference/collaboration_sort_clauses/): Sort Clauses available for Collaboration search # Collaborative editing product guide ## What is collaborative editing Collaborative editing is a feature that allows multiple users to work on the same simultaneously - whether to preview, review, or edit it. By giving users access to preview the content before it's published, review and collaboration become much easier. An additional option here is the ability to copy a link to the content item, which allows to share it through communication channels. It improves collaboration with external users, such as third-party agencies. A more advanced part of the collaboration feature is the Real-time editing. Users can edit and review content in real time, making teamwork faster, more efficient, and streamlining the content review process. The system automatically tracks changes, allowing seamless collaboration within a single content item. ## Availability Collaborative editing is available in all Ibexa DXP editions. To use Real-time editing feature, you must make arrangements with Ibexa, and accept Terms and Conditions and Service Level Agreement in the Support Portal. ## How does collaboration work ### Content preview The basic option provided by the Collaboration feature, is the ability to preview content. This allows the user to grant preview access to logged-in users, as well as share a public link with external users. You can share a direct link to the collaborative session using the **Copy link** button. Link is copied to the clipboard and you can share it with the users through communication channels. ### Collaboration session Collaborative editing allows to work together on the same content items. This is done through a collaboration session. When you create a new draft of a content item you can invite other users to join a collaboration session, thanks to [CKEditor collaboration features](https://ckeditor.com/ckeditor-5/capabilities/collaboration-features/). This action generates a unique session for that draft. Collaboration session begins when first invited user accepts the invitation and joins the session. To start collaborative editing, you need to invite collaborators using the **Share** button. The owner of the draft can invite other users to join the session, both internal and external: - **Internal** - by searching their name or email address. These users can either edit the content item or preview it, depending on your choice. - **External** - by providing their email address in the field. They can only preview the content item. Once they accept the invitation, they are able to join you in editing content item or reviewing it. Once the invitation is accepted, internal users can invite further users if their permissions allow for it. To do so, they must be a part of an active collaboration session. Gaining access to the draft through other methods, for example by [workflow](https://doc.ibexa.co/en/latest/content_management/workflow/workflow/index.md), doesn't allow you to invite other users. *[Image: Collaborative editing - invitation]* You can change the users access or remove it at any time. After inviting users to a collaboration session, they receive a notification visible on the main dashboard or by email. Users can also join a collaboration session using the **Join** button: - available in new tabs of the **My content** block on the dashboard - *My shared drafts* and *Drafts shared with me* - by accessing a content draft in the **Drafts** menu ### Real-time editing Real-time editing is an advanced part of the Collaboration feature. It works by syncing changes in real time, so everyone can see updates instantly. Avatars of the users invited to collaboration session are visible at the top of the editing screen, also in distraction free mode. While editing Rich Text fields, you can see colored tracking tags with user avatar thumbnails that indicate who is currently working on it. Everyone in the session can see each other's updates as they happen — no more switching between tools or waiting for feedback. This makes content creation and review faster, more interactive, and much easier to manage as a team. Users can edit the content only if an administrator gives them the necessary permissions. These permissions must be set before the user is invited to the session, otherwise the **Edit** access option is unavailable (grayed out). #### Editing content items Collaborative editing is enabled in [Rich Text](https://doc.ibexa.co/en/latest/content_management/rich_text/rich_text/index.md) fields. Other fields are disabled and can be only edited by the owner of the content item. Collaboration is available for the following content types with Rich Text fields: - Article - Folder - Form - Custom content types All changes made by collaborators are automatically saved when owner publishes or saves content. Collaborators can leave collaboration session any time without losing data. ## Benefits ### Simplified content review process With preview access to the content draft, reviewers can jump in, check the content, and approve it more quickly. This streamlines the entire review cycle and minimizes delays caused by version confusion or slow feedback. Review can be done even by external users without a need to set up an account in Ibexa DXP. ### Cross-functional collaboration Collaborative editing allows teams to involve users from across the organization. Their input can be integrated directly into the editing process, leading to more accurate and aligned content. ### Enhanced teamwork All the users invited to the collaboration session can share ideas, make suggestions, and refine each other’s work in a shared environment, creating a stronger sense of team ownership and collaboration. ### Real-time collaboration Collaborative editing enables multiple users to work on the same content item at the same time. Everyone in the session can see changes as they happen, which shortens the feedback loop and allows multiple people to work in parallel. ### Improved efficiency By allowing simultaneous editing, the content creation and review process becomes significantly faster. Team members can work together and finalize content in a fraction of the time it would take with a standard workflow. ### Seamless feedback loop Users can share and receive feedback instantly within the same editing interface. This reduces the need to switch between apps or track changes manually. # Collaborative editing Collaborative editing feature is available in Ibexa DXP starting with version v5.0.2 or higher, regardless of its edition. ## Installation ### Install Real-time editing feature package If you have an arrangements with Ibexa to use Real-time editing feature, you need to install following package: ``` composer require ibexa/fieldtype-richtext-rte ``` This command installs also `ibexa/ckeditor-premium` package and adds the new real-time editing functionality to the Rich Text field type. It also modifies the permission system to account for the new functionality. ### Modify the bundles file Then, if not using Symfony Flex, add the following code to the `config/bundles.php` file: ``` ['all' => true], Ibexa\Bundle\CkeditorPremium\IbexaCkeditorPremiumBundle::class => ['all' => true], ]; ``` ## Configure Collaborative editing Before you can start Collaborative editing feature, you must enable it by following these instructions. ### Security configuration After an installation process is finished, go to `config/packages/security.yaml` and make following changes: - uncomment following lines with `shared` user provider under the `providers` key: ``` security: providers: # ... shared: id: Ibexa\Collaboration\Security\User\ShareableLinkUserProvider ``` - uncomment following lines under the `ibexa_shareable_link` key: ``` security: # ... ibexa_shareable_link: request_matcher: Ibexa\Collaboration\Security\RequestMatcher\ShareableLinkRequestMatcher pattern: ^/ provider: shared stateless: true user_checker: Ibexa\Core\MVC\Symfony\Security\UserChecker custom_authenticators: - Ibexa\Collaboration\Security\Authenticator\ShareableLinkAuthenticator ``` ### Configuration You can configure Collaborative editing per [Repository](https://doc.ibexa.co/en/latest/administration/configuration/repository_configuration/index.md). Under `ibexa.repositories..collaboration` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files), indicate the settings for collaboration: ``` ibexa: repositories: : collaboration: participants: allowed_types: - internal - external auto_invite: session: public_link_enabled: ``` The following settings are available: - participants: - `allowed_types` - defines allowed user types, values: `internal`, `external`, you can set one or both of the values - `auto_invite` - determines whether invitations should be sent automatically when inviting someone to a session, default value: `true`, available values: `true`, `false` - session: - `public_link_enabled` - determines whether the public link is available, default value: `false`, available values: `true`, `false` #### `ibexa/share` configuration To share content model, you need to configure the `ibexa/share` package. Under `ibexa.system` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files), indicate the settings: ``` ibexa: system: admin_group: share: content_type_groups: - 'Content' excluded_content_types: - 'tag' - 'landing_page' - 'product_category_tag' ``` The following setting is available: - `content_type_groups` – defines groups of content types for which the **Share** button is displayed (it can still be disabled for specific content types within these groups by using the `excluded_content_types` setting) In the example configuration above, the **Share** button is displayed for any content that belongs to the `Content` group, except for `tag`, `landing_page`, and `product_category_tag` content types. You can also control which user content types can use the feature through the `ibexa.share.permission_check_context.content.user_content_type_identifiers` container parameter. It accepts an array of content type identifiers and the default value is `['editor']`. You can now restart you application and start working with the Collaborative editing feature. To add the real-time editing capabilities, continue with the instruction below. ## Configure real-time editing You must have an arrangement with Ibexa before configuring the real-time editing. If you haven't already, you must also accept the Terms of Service in the [Service portal](https://support.ibexa.co/). Only then you can create a new Collaborative editing environment. To do it, log in to the service portal, go to your **Service Portal** and select **Create environment** (this requires the **Portal administrator** access level). Once the environment is created, you can proceed with the configuration in Ibexa DXP. Use the generated values to set the `environment_id`, `environment_secret`, and `web_socket_url` for your [repositories](https://doc.ibexa.co/en/latest/administration/configuration/repository_configuration/index.md) as in the example below: ``` ibexa: repositories: default: fieldtype_richtext_rte: environment_id: '%env(CKEDITOR_ENVIRONMENT_ID)%' environment_secret: '%env(CKEDITOR_ENVIRONMENT_SECRET)%' web_socket_url: '%env(CKEDITOR_WEB_SOCKET_URL)%' ``` Then, enable real-time editing for specific [SiteAccesses](https://doc.ibexa.co/en/latest/multisite/siteaccess/siteaccess/index.md). The following example enables it for the back office: ``` ibexa: system: admin_group: fieldtype_richtext_rte: enabled: true ``` Finish the configuration by running: ``` composer run post-install-cmd ``` ## Accepting new Terms of Service Real-Time Collaboration service is only available after accepting its Terms and Conditions. Any new version of this document released by Ibexa must be accepted before the assigned deadline. The **Portal administrator** for your [Service portal](https://support.ibexa.co) can accept it in Service portal's service details. If not done in time, the Real-Time Collaboration service will be disabled until the latest Terms and Conditions are accepted. # Collaborative editing API Ibexa DXP's Collaborative editing API provides two services for managing sessions and invitations, which differ in function: - [`InvitationServiceInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-InvitationServiceInterface.html) is used to manage collaboration sessions invitations - [`SessionServiceInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-SessionServiceInterface.html) is used to manage collaboration sessions ## Managing sessions ### Create session You can create new collaboration session with [`SessionService::createSession()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-SessionServiceInterface.html#method_createSession): ``` $versionInfo = $this->contentService->loadContent(52)->getVersionInfo(); $createStruct = new ContentSessionCreateStruct( $versionInfo, $versionInfo->getInitialLanguage() ); $createStruct->setHasPublicLink(false); $token = 'my-secret-token-12345'; $createStruct->setToken($token); $sessionId = $this->sessionService->createSession($createStruct)->getId(); ``` ### Get session You can get an existing collaboration session with [`SessionService::getSession()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-SessionServiceInterface.html#method_getSession): - using given id - with [`SessionService::getSession()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-SessionServiceInterface.html#method_getSession) ``` $session = $this->sessionService->getSession($sessionId); ``` - using given token - with [`SessionService::getSessionByToken()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-SessionServiceInterface.html#method_getSessionByToken) ``` $session = $this->sessionService->getSessionByToken($token); ``` ### Find sessions You can find an existing session with [`SessionService::findSessions()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-SessionServiceInterface.html#method_findSessions) by passing a SessionQuery object: ``` $sessionQuery = new SessionQuery(new Token($token)); $session = $this->sessionService->findSessions($sessionQuery)->getFirst(); ``` To learn more about the available search options, see [Search Criteria](https://doc.ibexa.co/en/latest/search/collaboration_search_reference/collaboration_criteria/index.md) and [Sort Clauses](https://doc.ibexa.co/en/latest/search/collaboration_search_reference/collaboration_sort_clauses/index.md) for Collaborative editing. ### Update session You can update existing invitation with [`SessionService::updateSession()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-SessionServiceInterface.html#method_updateSession): ``` $updateStruct = new ContentSessionUpdateStruct(); $updateStruct->setHasPublicLink(true); $this->sessionService->updateSession($session, $updateStruct); ``` ### Delete session You can delete session with [`SessionService::deleteSession()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-SessionServiceInterface.html#method_deleteSession): ``` $this->sessionService->deleteSession($session); ``` ## Managing participants ### Add participant You can add participant to the collaboration session with [`SessionService::addParticipant()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-SessionServiceInterface.html#method_addParticipant): ``` $user = $this->userService->loadUserByLogin('another_user'); $internalParticipantCreateStruct = new InternalParticipantCreateStruct( $user, ContentSessionScope::VIEW ); $externalParticipantCreateStruct = new ExternalParticipantCreateStruct( 'external@example.com', ContentSessionScope::VIEW, 'personal-secret-token-12345' ); $internalParticipant = $this->sessionService->addParticipant($session, $internalParticipantCreateStruct); $externalParticipant = $this->sessionService->addParticipant($session, $externalParticipantCreateStruct); ``` ### Get and update participant You can update participant added to the collaboration session with [`SessionService::updateParticipant()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-SessionServiceInterface.html#method_updateParticipant): ``` $participant = $this->sessionService ->getSession($session->getId()) ->getParticipants() ->getByEmail($user->email); $internalParticipantUpdateStruct = new InternalParticipantUpdateStruct(ContentSessionScope::EDIT); $this->sessionService->updateParticipant($session, $participant, $internalParticipantUpdateStruct); ``` The example below updates participant's permissions to allow for editing of shared content, not only previewing. ### Remove participant You can remove participant from the collaboration session with [`SessionService::removeParticipant()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-SessionServiceInterface.html#method_removeParticipant): ``` $this->sessionService->removeParticipant($session, $externalParticipant); ``` ### Check session owner You can check whether a user belongs to a collaboration session with [`SessionService::isSessionOwner()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-SessionServiceDecorator.html#method_isSessionOwner): ``` $this->sessionService->isSessionOwner( $session, $this->userService->loadUserByLogin('another_user') ); ``` If no user is provided, current user is used. ### Check session participant You can check the participant of the collaboration session with [`SessionService::isSessionParticipant()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-SessionServiceInterface.html#method_isSessionParticipant): ``` $this->sessionService->isSessionParticipant( $session, $this->permissionResolver->getCurrentUserReference() ); ``` ## Managing invitations ### Manage invitation You can get an invitation with [`InvitationService::getInvitation()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-InvitationServiceInterface.html#method_getInvitation): ``` $invitation = $this->invitationService->getInvitationByParticipant($participant); ``` ### Create invitation You can create new invitation for the collaborative session using the [`InvitationService::createInvitation()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-InvitationServiceInterface.html#method_createInvitation) method: ``` $invitationCreateStruct = new InvitationCreateStruct( $session, $internalParticipant ); $this->invitationService->createInvitation($invitationCreateStruct); ``` You can use it when auto-inviting participants is not enabled. ### Update invitation You can update existing invitation with [`InvitationService::updateInvitation()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-InvitationServiceInterface.html#method_updateInvitation): ``` $invitationUpdateStruct = new InvitationUpdateStruct(); $invitationUpdateStruct->setStatus(InvitationStatus::STATUS_REJECTED); $this->invitationService->updateInvitation($invitation, $invitationUpdateStruct); ``` ### Delete invitation You can delete an invitation with [`InvitationService::deleteInvitation()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-InvitationServiceInterface.html#method_deleteInvitation): ``` $invitation = $this->invitationService->getInvitation(2); $this->invitationService->deleteInvitation($invitation); ``` ### Find invitations You can find an invitation with [`InvitationService::findInvitations()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-InvitationServiceInterface.html#method_findInvitations): ``` $invitationQuery = new InvitationQuery(new Session($session)); $invitations = $this->invitationService->findInvitations($invitationQuery)->getInvitations(); foreach ($invitations as $invitation) { $output->writeln('Invitation ID: ' . $invitation->getId() . ' Status: ' . $invitation->getStatus()); } ``` To learn more about the available search options, see [Search Criteria](https://doc.ibexa.co/en/latest/search/collaboration_search_reference/collaboration_criteria/index.md) and [Sort Clauses](https://doc.ibexa.co/en/latest/search/collaboration_search_reference/collaboration_sort_clauses/index.md) for Collaborative editing. ## Example API usage Below you can see an example of API usage for Collaborative editing: ``` permissionResolver->setCurrentUserReference( $this->userService->loadUserByLogin('admin') ); // Create a sharing session for Content $versionInfo = $this->contentService->loadContent(52)->getVersionInfo(); $createStruct = new ContentSessionCreateStruct( $versionInfo, $versionInfo->getInitialLanguage() ); $createStruct->setHasPublicLink(false); $token = 'my-secret-token-12345'; $createStruct->setToken($token); $sessionId = $this->sessionService->createSession($createStruct)->getId(); // Get a session by ID or token $session = $this->sessionService->getSession($sessionId); $session = $this->sessionService->getSessionByToken($token); // Find sessions $sessionQuery = new SessionQuery(new Token($token)); $session = $this->sessionService->findSessions($sessionQuery)->getFirst(); // Update a session $updateStruct = new ContentSessionUpdateStruct(); $updateStruct->setHasPublicLink(true); $this->sessionService->updateSession($session, $updateStruct); // Deactivate a session $updateStruct = new ContentSessionUpdateStruct(); $updateStruct->setIsActive(false); $this->sessionService->updateSession($session, $updateStruct); // Manage participants $user = $this->userService->loadUserByLogin('another_user'); $internalParticipantCreateStruct = new InternalParticipantCreateStruct( $user, ContentSessionScope::VIEW ); $externalParticipantCreateStruct = new ExternalParticipantCreateStruct( 'external@example.com', ContentSessionScope::VIEW, 'personal-secret-token-12345' ); $internalParticipant = $this->sessionService->addParticipant($session, $internalParticipantCreateStruct); $externalParticipant = $this->sessionService->addParticipant($session, $externalParticipantCreateStruct); // Get and update participants $participant = $this->sessionService ->getSession($session->getId()) ->getParticipants() ->getByEmail($user->email); $internalParticipantUpdateStruct = new InternalParticipantUpdateStruct(ContentSessionScope::EDIT); $this->sessionService->updateParticipant($session, $participant, $internalParticipantUpdateStruct); // Remove participant $this->sessionService->removeParticipant($session, $externalParticipant); // Check ownerships. If no user is provided, current user is used. $this->sessionService->isSessionOwner( $session, $this->userService->loadUserByLogin('another_user') ); // Check participation $this->sessionService->isSessionParticipant( $session, $this->permissionResolver->getCurrentUserReference() ); // Manage invitations $invitationQuery = new InvitationQuery(new Session($session)); $invitations = $this->invitationService->findInvitations($invitationQuery)->getInvitations(); foreach ($invitations as $invitation) { $output->writeln('Invitation ID: ' . $invitation->getId() . ' Status: ' . $invitation->getStatus()); } $invitation = $this->invitationService->getInvitationByParticipant($participant); // Create invitation - use when auto-inviting participants is not enabled $invitationCreateStruct = new InvitationCreateStruct( $session, $internalParticipant ); $this->invitationService->createInvitation($invitationCreateStruct); // Update invitation $invitationUpdateStruct = new InvitationUpdateStruct(); $invitationUpdateStruct->setStatus(InvitationStatus::STATUS_REJECTED); $this->invitationService->updateInvitation($invitation, $invitationUpdateStruct); // Delete invitation $invitation = $this->invitationService->getInvitation(2); $this->invitationService->deleteInvitation($invitation); // Delete a session $this->sessionService->deleteSession($session); return Command::SUCCESS; } } ``` # Extend Collaborative editing Thanks to the ability to extend the [Collaborative editing](https://doc.ibexa.co/en/latest/content_management/collaborative_editing/collaborative_editing_guide/index.md) feature, you can introduce additional functionalities to enhance workflows not only in the context of content editing but also when working with products. The example below demonstrates how to extend the feature to enable a shared Cart functionality in the Commerce system. > **Tip: Tip** > > If you prefer learning from videos, watch the Ibexa Summit 2025 presentation that covers the Collaborative editing feature: > > [*Collaboration: greater than the sum of the parts*](https://www.youtube.com/watch?v=dRB-SDlgX0I) by Marek Nocoń ## Create tables to hold Cart session data First, set up the database layer and define the collaboration context, in this example, Cart. Create the necessary tables to store the data and to link the collaboration session with the Cart you want to share. In the `data/schema.sql` file, create a database table to store a reference to the session context. In this example, the context is a shopping Cart, identified by `cart_identifier` and linked to the collaboration session through the Cart’s numeric ID stored in the database. **MySQL** ``` CREATE TABLE ibexa_collaboration_cart ( id INT NOT NULL PRIMARY KEY, cart_identifier VARCHAR(255) NOT NULL, CONSTRAINT ibexa_collaboration_cart_ibexa_collaboration_id_fk FOREIGN KEY (id) REFERENCES ibexa_collaboration (id) ON DELETE CASCADE ) COLLATE = utf8mb4_general_ci; ``` **PostgreSQL** ``` CREATE TABLE ibexa_collaboration_cart ( id INTEGER NOT NULL PRIMARY KEY, cart_identifier VARCHAR(255) NOT NULL, CONSTRAINT ibexa_collaboration_cart_ibexa_collaboration_id_fk FOREIGN KEY (id) REFERENCES ibexa_collaboration (id) ON DELETE CASCADE ); ``` ## Set up persistence layer Now you need to prepare the persistence layer, which is responsible for storing, retrieving, and managing collaboration session and Cart data in the database. It ensures that when a user creates, joins, or updates a Cart session, the system can track session status, participants, and permissions. ### Implement persistence gateway The Gateway is the layer that connects the collaboration feature to the database. It handles all the create, read, update, and delete operations for collaboration sessions, ensuring that session data is stored and retrieved correctly. It also uses a Discriminator to specify the session type. Based on the type, the Gateway interacts with the appropriate tables and data structures. This way, the system uses the correct Gateway to get or save data for each session type. When creating the Database Gateways and mappers, you can use the built-in service tag: - `ibexa.collaboration.persistence.session.gateway` - for the database gateway: ``` tags: - { name: 'ibexa.collaboration.persistence.session.gateway', discriminator: 'my_session_type' } ``` - `ibexa.collaboration.persistence.session.mapper` - for the mapper that creates a session from a persistence raw row: ``` tags: - { name: 'ibexa.collaboration.persistence.session.mapper', discriminator: 'my_session_type' } ``` - `ibexa.collaboration.service.session.domain.mapper` - for the mapper that creates a session from a persistence object: ``` tags: - { name: 'ibexa.collaboration.service.session.domain.mapper', type: App\…\MyPersistentSession } ``` - `ibexa.collaboration.service.session.persistence.mapper` - for the mapper that converts a session into a structure used to create or update persistence: ``` tags: - { name: 'ibexa.collaboration.service.session.persistence.mapper', type: 'my_session_type' } ``` In the `src/Collaboration/Cart/Persistence/Gateway/` directory, create the following files: - `DatabaseSchema` - defines the database tables needed to store shared Cart collaboration session data: ``` * * @template-implements \Ibexa\Collaboration\Persistence\Session\Inner\GatewayInterface */ final class DatabaseGateway extends AbstractDoctrineDatabase implements GatewayInterface { public const string DISCRIMINATOR = 'cart'; protected function buildMetadata(): DoctrineSchemaMetadataInterface { return new DoctrineSchemaMetadata( $this->connection, null, $this->getTableName(), [ DatabaseSchema::COLUMN_ID => Types::INTEGER, DatabaseSchema::COLUMN_CART_IDENTIFIER => Types::STRING, ], [DatabaseSchema::COLUMN_ID] ); } protected function getTableName(): string { return DatabaseSchema::TABLE_NAME; } public function getDiscriminator(): string { return self::DISCRIMINATOR; } /** * @param \App\Collaboration\Cart\Persistence\Values\CartSessionCreateStruct $createStruct */ public function create(int $sessionId, AbstractSessionCreateStruct $createStruct): void { $this->doInsert([ DatabaseSchema::COLUMN_ID => $sessionId, DatabaseSchema::COLUMN_CART_IDENTIFIER => $createStruct->getCartIdentifier(), ]); } /** * @param \Ibexa\Collaboration\Persistence\Values\AbstractSessionUpdateStruct $updateStruct */ public function update(AbstractSessionUpdateStruct $updateStruct): void { // There is nothing to update } } ``` ### Define persistence Value objects Value objects describe how collaboration session data is represented in the database. Persistence gateway uses them to store, retrieve, and manipulate session information, such as the session ID, associated Cart, participants, and scopes. ``` App\Collaboration\Cart\Mapper\CartSessionDomainMapper: tags: - name: 'ibexa.collaboration.service.session.domain.mapper' type: App\Collaboration\Cart\Persistence\Values\CartSession ``` In the `src/Collaboration/Cart/Persistence/Values/` directory, create the following Value Objects: - `CartSession` - represents the Cart collaboration session data: ``` cartIdentifier; } } ``` - `CartSessionCreateStruct` - defines the data needed to create a new Cart collaboration session: ``` cartIdentifier; } public function setCartIdentifier(string $cartIdentifier): void { $this->cartIdentifier = $cartIdentifier; } public function getDiscriminator(): string { return CartSessionType::IDENTIFIER; } } ``` - `CartSessionUpdateStruct` - defines the data used to update an existing Cart collaboration session: ``` cart; } public function setCart(CartInterface $cart): void { $this->cart = $cart; } public function getType(): string { return CartSessionType::IDENTIFIER; } } ``` - `CartSessionUpdateStruct` - defines the properties used to update an existing Cart collaboration session, including participants, scopes, and metadata: ``` cart; } } ``` - `CartSessionType` - defines the type of the collaboration session (in this case it indicates it’s a Cart session): ``` getScopes(), true); } public function getScopes(): array { return [ self::SCOPE_VIEW, self::SCOPE_EDIT, ]; } } ``` ## Create mappers Mappers convert session data into the format required by the database and pass it to the repository. In the `src/Collaboration/Cart/Mapper/` directory, create following mappers: - `CartProxyMapper` - creates a simplified version of the Cart with only the necessary data to reduce memory usage in collaboration sessions: ``` repository->sudo(fn (): CartInterface => $this->cartService->getCart($identifier)); return true; }; return $this->proxyGenerator->createProxy(CartInterface::class, $initializer); } } ``` - `CartProxyMapperInterface` - defines how a Cart should be converted into a simplified object that is used in collaboration session and specifies what methods the mapper must implement: ``` */ final readonly class CartSessionDomainMapper implements SessionDomainMapperInterface { public function __construct( private CartProxyMapperInterface $cartProxyMapper, private UserProxyDomainMapperInterface $userDomainMapper, private ParticipantCollectionDomainMapperInterface $participantCollectionDomainMapper ) { } /** * @param \App\Collaboration\Cart\Persistence\Values\CartSession $data */ public function fromPersistence(SessionData $data): SessionInterface { return new CartSession( $data->getId(), $this->cartProxyMapper->createCartProxy($data->getCartIdentifier()), $data->getToken(), $this->userDomainMapper->createUserProxy($data->getOwnerId()), $this->participantCollectionDomainMapper->createParticipantCollectionProxy($data->getId()), $data->isActive(), $data->hasPublicLink(), $data->getCreatedAt(), $data->getUpdatedAt(), ); } } ``` - `CartSessionPersistenceMapper` - prepares session data to be saved or updated in the database: ``` getToken(); $owner = $createStruct->getOwner(); $hasPublicLink = $createStruct->hasPublicLink(); assert($token !== null); assert($owner !== null); assert($hasPublicLink !== null); return new CartSessionCreateStruct( $token, $createStruct->getCart()->getIdentifier(), $owner->getUserId(), $createStruct->isActive(), $hasPublicLink, new \DateTimeImmutable(), new \DateTimeImmutable() ); } public function toPersistenceUpdateStruct( SessionInterface $session, SessionUpdateStruct $updateStruct ): PersistenceSessionUpdateStruct { return new CartSessionUpdateStruct( $session->getId(), $updateStruct->getToken(), ($updateStruct->getOwner() ?? $session->getOwner())->getUserId() ); } } ``` Then, in the `src/Collaboration/Cart/Persistence/` directory, create the following mapper: - `Persistence/Mapper` - builds the session object from persistence row: ``` */ final class Mapper implements MapperInterface { public function extractFromRow(array $row): AbstractSession { return new CartSession( $row['id'], $row['cart_cart_identifier'], $row['token'], $row['owner_id'], $row['is_active'], $row['has_public_link'], $row['created_at'], $row['updated_at'] ); } } ``` In `services.yaml`, declare and tags the gateway and the mappers: ``` services: # … App\Collaboration\Cart\Persistence\Gateway\DatabaseGateway: arguments: $connection: '@ibexa.persistence.connection' tags: - name: 'ibexa.collaboration.persistence.session.gateway' discriminator: !php/const App\Collaboration\Cart\Persistence\Gateway\DatabaseGateway::DISCRIMINATOR App\Collaboration\Cart\Persistence\Mapper: tags: - name: 'ibexa.collaboration.persistence.session.mapper' discriminator: !php/const App\Collaboration\Cart\Persistence\Gateway\DatabaseGateway::DISCRIMINATOR App\Collaboration\Cart\Mapper\CartSessionDomainMapper: tags: - name: 'ibexa.collaboration.service.session.domain.mapper' type: App\Collaboration\Cart\Persistence\Values\CartSession App\Collaboration\Cart\Mapper\CartSessionPersistenceMapper: tags: - name: 'ibexa.collaboration.service.session.persistence.mapper' type: !php/const App\Collaboration\Cart\CartSessionType::IDENTIFIER ``` ## Allow participants to access Cart To enable collaboration, you must configure the appropriate permissions. This involves decorating the `PermissionResolver` and `CartResolver`. This ensures that when a Cart is part of a Cart collaboration session, users can access it based on the defined permissions. In all other cases, the system falls back to the default implementation. > **Caution: Decorating permissions** > > When decorating permissions, be careful to change the behavior only as necessary, to ensure that the Cart is shared only with the intended users. In the `src/Collaboration/Cart/` directory, create the following files: - `PermissionResolverDecorator` – customizes the permission resolver to handle access rules for Cart collaboration sessions. It allows participants to view or edit shared Carts while preserving default permission checks for all other cases. Here you can decide what scope is available for this collaboration session by choosing between `view` or `edit`: ``` getObject(); if ($this->nested === false && $this->isCartPolicy($policy) && $object instanceof CartInterface && $this->isSharedCart($object)) { return true; } return $this->innerPermissionResolver->canUser($policy); } public function assertPolicy(PolicyInterface $policy): void { $object = $policy->getObject(); if ($this->nested === false && $this->isCartPolicy($policy) && $object instanceof CartInterface && $this->isSharedCart($object)) { return; } $this->innerPermissionResolver->assertPolicy($policy); } private function isCartPolicy(PolicyInterface $policy): bool { return $policy instanceof CartView || $policy instanceof CartEdit; } private function isSharedCart(?CartInterface $cart): bool { if ($cart === null) { return false; } try { $this->nested = true; /** @var \App\Collaboration\Cart\CartSession $session */ $session = $this->getCurrentCartCollaborationSession(); if ($session !== null) { try { return $cart->getId() === $session->getCart()->getId(); } catch (NotFoundException) { } } } finally { $this->nested = false; } return false; } private function getCurrentCartCollaborationSession(): ?CartSession { $token = $this->requestStack->getSession()->get(self::COLLABORATION_SESSION_ID); if ($token === null) { return null; } try { $session = $this->sessionService->getSessionByToken($token); if ($session instanceof CartSession) { return $session; } } catch (NotFoundException|UnauthorizedException) { } return null; } } ``` - `CartResolverDecorator` – resolves the shared Carts in collaboration sessions by checking if a Cart belongs to a collaboration session: ``` hasSharedCart()) { return $this->getSharedCart() ?? $this->innerCartResolver->resolveCart($user); } return $this->innerCartResolver->resolveCart($user); } private function getSharedCart(): ?CartInterface { try { $session = $this->sessionService->getSessionByToken( $this->requestStack->getSession()->get(PermissionResolverDecorator::COLLABORATION_SESSION_ID) ); if (!$session instanceof CartSession) { return null; } return $session->getCart(); } catch (NotFoundException|UnauthorizedException) { return null; } } private function hasSharedCart(): bool { return $this->requestStack->getSession()->has(PermissionResolverDecorator::COLLABORATION_SESSION_ID); } } ``` In `services.yaml`, declare those decorator services associated with what they decorate: ``` services: # … App\Collaboration\Cart\PermissionResolverDecorator: decorates: Ibexa\Contracts\ProductCatalog\PermissionResolverInterface App\Collaboration\Cart\CartResolverDecorator: decorates: Ibexa\Contracts\Cart\CartResolverInterface ``` ## Build dedicated controllers to manage Cart sharing flow To support Cart sharing, create controllers which handle the collaboration flow. They are responsible for starting a sharing session, adding participants, and allowing users to join an existing shared Cart. You need to create two controllers: - `ShareCartCreateController` - creates the Cart collaboration session and adds participants - `ShareCartJoinController` - allows to join the session ### `ShareCartCreateController` This controller handles the request when you enter an email address of the user that you want to invite and submit it. It captures the email address and checks whether the form has been submitted. If yes, the form data is retrieved, and the `cartResolver` verifies whether there is currently a shared Cart. If a shared Cart exists, the Cart is retrieved and a session is created (`$cart` becomes the session context). In the `addParticipant` step, the user whose email address was provided is added to the session and assigned a scope (either `view` or `edit`). ``` createForm( ShareCartType::class, null, [ 'method' => 'POST', ] ); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { /** @var \App\Form\Data\ShareCartData $data */ $data = $form->getData(); // Handle the form submission $cart = $this->cartResolver->resolveCart(); $session = $this->sessionService->createSession( new CartSessionCreateStruct($cart) ); $email = $data->getEmail(); if ($email === null) { throw new InvalidArgumentException('Email cannot be null'); } $this->sessionService->addParticipant( $session, new ExternalParticipantCreateStruct( $email, CartSessionType::SCOPE_EDIT ) ); return $this->render( '@ibexadesign/cart/share_result.html.twig', [ 'session' => $session, ] ); } return $this->render( '@ibexadesign/cart/share.html.twig', [ 'form' => $form->createView(), ] ); } } ``` ### `ShareCartJoinController` It enables joining a Cart session. The session token created earlier is passed in the URL, and in the `join` action, the system attempts to retrieve the session associated with that token. If the token is invalid, an exception is thrown to indicate that the session cannot be accessed. If the session exists, the session parameter (`collaboration_session`) is retrieved and the session stores the token. Finally, `redirectToRoute` redirects the user to the Cart view and passes the identifier of the shared Cart. ``` sessionService->getSessionByToken($token); if ($session instanceof CartSession) { $request->getSession()->set(self::CURRENT_COLLABORATION_SESSION, $session->getToken()); return $this->redirectToRoute('ibexa.cart.view', [ 'identifier' => $session->getCart()->getIdentifier(), ]); } throw $this->createAccessDeniedException(); } } ``` > **Caution: Session parameter** > > Avoid using a generic session parameter name such as `collaboration_session` (it's used here only for example purposes). The user can participate in multiple sessions simultaneously (of one or many types), so using such name would cause the parameter to be constantly overwritten. Therefore, active sessions should not be resolved based on such parameter. ## Integrate with Symfony forms by adding forms and templates To support inviting users to a shared Cart, you need to create a dedicated form and a data class. The form collects the email address of the user that you want to invite, and the data class is used to safely pass that information from the form to the controller. - `ShareCartType` - a simple form for entering an email address of the user you want to invite to share the Cart. The form contains a single input field where you enter the email address manually: ``` */ final class ShareCartType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options): void { $builder->add('email', EmailType::class, [ 'label' => 'E-mail', ])->add('submit', SubmitType::class, [ 'label' => 'Share', ]); } public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'data_class' => ShareCartData::class, ]); } } ``` - `ShareCartData` - a class that holds the email address submitted through the form and passes it to the controller: ``` email; } public function setEmail(?string $email): void { $this->email = $email; } } ``` The last step is to integrate the new session type into your application by adding templates. In this step, the view is rendered. You need to add the following Twig templates in the `src/templates/themes/storefront/cart/` directory: - `share` - defines the view for the Cart sharing form. It renders the form where a user can enter an email address to invite someone to collaborate on the Cart: ``` {% extends '@ibexadesign/storefront/layout.html.twig' %} {% block content %} {{ form_start(form) }} {{ form_label(form.email, null, { 'label_attr': { 'class': 'ibexa-store-label' }}) }} {{ form_widget(form.email, { 'attr': { 'class': 'ibexa-store-input' } }) }} {{ form_widget(form.submit, { 'attr': { 'class': 'ibexa-store-btn ibexa-store-btn--primary' } }) }} {{ form_end(form) }} {% endblock %} ``` *[Image: Share email]* - `share_result` - renders the result page after a Cart has been shared. If the shared Cart exists in the system, the created session object is passed to the view and displayed. A message like "Cart has been shared…" is displayed, along with a link to access the session: ``` {% extends '@ibexadesign/storefront/layout.html.twig' %} {% block content %}

    Cart has been shared successfully! Link to session:  {{ url('app.shared_cart.join', { token: session.getToken() }) }}

    {% endblock %} ``` *[Image: Share message]* - `view` - shows the Cart page. It displays the Cart content and includes the “Share Cart” button: ``` {% extends '@IbexaStorefront/themes/storefront/cart/view.html.twig' %} {% block content %} {{ parent() }} {% endblock %} ``` *[Image: Share button]* # Templating # Templating To create a website front for your site, you can use the Twig-based templating system. You configure the templates to use by applying content view configuration that covers for different types of content and different parts of your website. The design engine lets you prepare differing template themes. Content view configuration is SiteAccess-aware. - [Templates](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/templating/templates/templates/): Ibexa DXP uses the Twig template engine to customize the rendering of content in the site. - [Content queries](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/templating/queries_and_controllers/content_queries/): Query content by using Query types and content query field. - [Twig function reference](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/templating/twig_function_reference/twig_function_reference/): Built-in Twig functions speed up rendering content in Twig templates. - [Design engine](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/templating/design_engine/design_engine/): Design engine allows you to use different SiteAccess-aware themes in your site. # Render content Content is rendered automatically by using default, basic templates. To render content with a custom template, you create a template file and inform the system, through configuration, when to use this template. You do it by using the [content view configuration](https://doc.ibexa.co/en/latest/templating/templates/template_configuration/index.md). For example, to apply a custom template to all articles, add the following [configuration](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa: system: site_group: content_view: full: article: template: '@ibexadesign/full/article.html.twig' match: Identifier\ContentType: article ``` This configuration defines a `full` view for all content items that fulfill the conditions in `match`. `match` indicates that all content items with the content type `article` should use this configuration. The indicated `template` is `@ibexadesign/full/article.html.twig`. > **Tip: Designs** > > This configuration uses the [design engine](https://doc.ibexa.co/en/latest/templating/design_engine/design_engine/index.md), as indicated by the `@ibexadesign` in the template path. In this example, the theme used by the design is `my_theme`. > > Using the design engine is recommended, but you can also set direct paths to templates, for example: > > ``` > template: 'full/article.html.twig' > ``` > > You must then ensure that the `templates/full` folder contains the template file. The configuration requires that you add the `article.html.twig` template file to `templates/themes//full`, in this example, `templates/themes/my_theme/full`. ```

    {{ ibexa_content_name(content) }}

    {{ content.contentInfo.publishedDate|ibexa_full_datetime }} {{ ibexa_render_field(content, 'intro') }} {{ ibexa_render_field(content, 'body', { 'attr': { class: 'article-body' } }) }} {{ ibexa_render_field(content, 'author', { 'template': '@ibexadesign/fields/author.html.twig' }) }} ``` ## Get content information To render general content information, such as content name, use the [`ibexa_content_name()`](https://doc.ibexa.co/en/latest/templating/twig_function_reference/content_twig_functions/#ibexa_content_name) Twig function. Content name is based on the [content name pattern](https://doc.ibexa.co/en/latest/administration/content_organization/content_types/#content-type-metadata) of the content type. ```

    {{ ibexa_content_name(content) }}

    ``` You can get general information about the content, location and view parameters by using the [available variables](https://doc.ibexa.co/en/latest/templating/templates/templates/#template-variables). For example, to get the publication date of the current content item, use: ``` {{ content.contentInfo.publishedDate|ibexa_full_datetime }} ``` > **Tip: Tip** > > For development purposes, you can list all available variables, or a single variable, and their values, by using the `dump()` Twig function: > > ``` > {{ dump() }} > {{ dump(content) }} > ``` ## Render fields You can render a single field of a content item by using the [`ibexa_render_field()`](https://doc.ibexa.co/en/latest/templating/twig_function_reference/field_twig_functions/#ibexa_render_field) Twig function. It takes the content item and the identifier of the Field as arguments: ``` {{ ibexa_render_field(content, 'intro') }} ``` You can pass additional arguments to this function, for example, an HTML class: ``` {{ ibexa_render_field(content, 'body', { 'attr': { class: 'article-body' } }) }} ``` ### Field templates You can use a custom Field template by passing the template as an argument to [`ibexa_render_field()`](https://doc.ibexa.co/en/latest/templating/twig_function_reference/field_twig_functions/#ibexa_render_field): ``` {{ ibexa_render_field(content, 'author', { 'template': '@ibexadesign/fields/author.html.twig' }) }} ``` In this case you must place the `author.html.twig` template in `templates/themes//fields`, for example `templates/themes/my_theme/fields`. ``` {% block ibexa_author_field %} {% if field.value.authors|length() > 0 %} {% for author in field.value.authors %} {{ author.name }} {% endfor %} {% endif %} {% endblock %} ``` The field template must be placed in a block that corresponds to the field type identifier, in this case `{% block ezauthor_field %}`. > **Tip: Template blocks** > > Twig blocks are used to include templates in one another. For more information about relationships between templates, see [Connecting templates](https://doc.ibexa.co/en/latest/templating/templates/templates/#connecting-templates). # Render a page Editions: Experience Page is a special content type that contains a [page field](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/pagefield/index.md). A page field is a layout composed of zones. Each zone can contain multiple blocks. ## Render a layout ### Configure layout The default, built-in page layout has only one zone. You can create other layouts in configuration, under the `ibexa_fieldtype_page.layouts` key. To create a new layout called "Right sidebar", use the following [configuration](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa_fieldtype_page: layouts: sidebar: identifier: sidebar name: Right sidebar description: Main section with sidebar on the right thumbnail: /assets/images/layouts/sidebar.png template: '@ibexadesign/layouts/sidebar.html.twig' zones: first: name: First zone second: name: Second zone ``` ### Add layout template A layout template renders all the zones of the layout. Each zone must have a `data-ibexa-zone-id` attribute with the number of the zone. The best way to display blocks in the zone is to iterate over a blocks array and render the blocks in a loop. Each block must have the `landing-page__block block_{{ block.type }}` classes and the `data-ibexa-block-id="{{ block.id }}` attribute. To render the "Right sidebar" layout, add the following template to `templates/themes/my_theme/layouts/sidebar.html.twig`: ```
    {% if zones[0].blocks %} {% for block in zones[0].blocks %}
    {{ render_esi(controller('Ibexa\\Bundle\\FieldTypePage\\Controller\\BlockController::renderAction', { 'contentId': contentInfo.id, 'blockId': block.id, 'versionNo': versionInfo.versionNo, 'languageCode': field.languageCode })) }}
    {% endfor %} {% endif %}
    {% if zones[1].blocks %} {% for block in zones[1].blocks %}
    {{ render_esi(controller('Ibexa\\Bundle\\FieldTypePage\\Controller\\BlockController::renderAction', { 'contentId': contentInfo.id, 'blockId': block.id, 'versionNo': versionInfo.versionNo, 'languageCode': field.languageCode })) }}
    {% endfor %} {% endif %}
    ``` ## Render a block Every built-in page block has a default template, [which you can override](#override-default-block-templates). Every page block can also have multiple other templates. The editor chooses a template when creating a block in the Page Builder. > **Caution: Clear the persistence cache** > > Persistence cache must be cleared after any modifications have been made to the block config in Page Builder, such as adding, removing or altering the page blocks, block attributes, validators or views configuration. > > To clear the persistence cache run `./bin/console cache:pool:clear [cache-pool]` command. The default cache-pool is named `cache.tagaware.filesystem`. The default cache-pool when running Redis or Valkey is named `cache.redis`. If you have customized the [persistence cache configuration](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/persistence_cache/#what-is-cached), the name of your cache pool might be different. > > In prod mode, you also need to clear the symfony cache by running `./bin/console c:c`. In dev mode, the Symfony cache is rebuilt automatically. ### Block configuration You can add new block templates by using configuration, for example, for the Content List block: ``` ibexa_fieldtype_page: blocks: contentlist: views: custom: template: '@ibexadesign/blocks/contentlist.html.twig' name: Custom content list ``` > **Tip: Tip** > > Use the same configuration to provide a template for [custom blocks](https://doc.ibexa.co/en/latest/content_management/pages/create_custom_page_block/index.md) you create. ### Block template Create the block template file in the provided path, for example, `templates/themes/my_theme/blocks/contentlist.html.twig`: ```

    {{ parentName }}

    {% if contentArray|length > 0 %}
    {% for content in contentArray %} {% endfor %}
    {% endif %}
    ``` ### Override default block templates To override the default block template, create a new template. Place it in a path that mirrors the original default template from the bundle folder. For example: `templates/bundles/IbexaFieldTypePageBundle/blocks/contentlist.html.twig`. > **Tip: Tip** > > To use a different file structure when overriding default templates, add an import statement to the template. > > For example, in `templates/bundles/IbexaFieldTypePageBundle/blocks/contentlist.html.twig`: > > ``` > {% import 'templates/blocks/contentlist/new_default.html.twig'} > ``` > > Then, place the actual template in the imported file `templates/blocks/contentlist/new_default.html.twig`. # Customize product view Editions: Commerce The built-in storefront offers a set of templates covering all functionalities of a shop, divided into smaller components. To customize your shop, you can override either whole templates, or specific components. The built-in templates belong to the `storefront` [theme](https://doc.ibexa.co/en/latest/templating/design_engine/design_engine/index.md). To override any of them, copy its directory structure in your template directory. > **Note: Note** > > By default, the anonymous user doesn't have permissions to view products. To change this, add the `Product/View` Policy to the Anonymous role. ## Template customization example As an example, to modify the template used to display the product price, you need to override [`vendor/ibexa/storefront/src/bundle/Resources/views/themes/storefront/storefront/component/price/price.html.twig`](https://github.com/ibexa/storefront/blob/v4.4.0/src/bundle/Resources/views/themes/storefront/storefront/component/price/price.html.twig) template. To do it, create your own template in `templates/themes/storefront/storefront/component/price/price.html.twig` file: ``` {% trans_default_domain 'storefront' %} {% set price = product.price %} {% if price is not null %}
    {{ 'ibexa_storefront.product_card.price.title'|trans({ '%price%': price })|desc('your price %price%') }}
    {% else %}
    {{ 'ibexa_storefront.product_card.price.unavailable'|trans()|desc('price currently unavailable') }}
    {% endif %} ``` This template adds a "price currently unavailable" label when a product doesn't have a price specified. ## Available templates All the storefront templates are located in `vendor/ibexa/storefront/src/bundle/Resources/view/themes/storefront`. The most important templates related to product rendering are: | Template | Component | | --------------------------------------------- | ------------------------------------ | | `storefront/catalog.html.twig` | main catalog and category view | | `storefront/component/product_view.html.twig` | full-screen view of a single product | ### Single product view | Template | Component | | --------------------------------------------------- | -------------------------------------- | | `storefront/component/product_assets.html.twig` | image asset preview and thumbnail list | | `storefront/component/product_attributes.html.twig` | listing of product attributes | ### Product list | Template | Component | | ------------------------------------------------------- | ------------------------------------------- | | `storefront/component/product_grid.html.twig` | grid for presenting products in the catalog | | `storefront/component/product_search_filters.html.twig` | panel with search filters | | `storefront/component/product_search_query.html.twig` | search box | | `storefront/component/product_search_sort.html.twig` | sorting drop-down | ### Images | Template | Component | | ------------------------------------------------- | ------------------------- | | `storefront/component/image_placeholder.svg.twig` | product image placeholder | > **Tip: Tip** > > For templates related to general storefront layout, cart and checkout, see [Customize storefront layout](https://doc.ibexa.co/en/latest/templating/layout/customize_storefront_layout/#available-templates). # Render content in PHP While in PHP, you may need to render the view of a content item (for example, for further treatment like PDF conversion, or because you're not in an HTML context). > **Caution: Caution** > > Avoid using PHP rendering in a controller as much as possible. You can access a view directly through the route `/view/content/{contentId}/{viewType}[/{location}]`. For example, on a fresh installation, you can access `/view/content/52/line` which returns a small piece of HTML with a link to the content that could be used in Ajax. If you need a controller to have additional information available in the template or to customize the `Response` object, define the controller in a [view configuration](https://doc.ibexa.co/en/latest/templating/templates/template_configuration/index.md) as shown in [Controllers](https://doc.ibexa.co/en/latest/templating/queries_and_controllers/controllers/index.md), enhance the View object and return it. The following example is a command outputting the render of a content for a view type in the terminal. It works only if the view doesn't refer to the HTTP request. It's compatible with the default installation views such as `line` or `embed`. To go further with this example, you could add some dedicated views not outputting HTML but, for example, plain text, [Symfony command styled text](https://symfony.com/doc/7.4/console/coloring.html) or Markdown. It doesn't work with a `full` view when the [page layout](https://doc.ibexa.co/en/latest/templating/templates/template_configuration/#page-layout) uses `app.request`, such as the out-of-the-box template. Create the command in `src/Command/ViewCommand.php`: ``` addOption('content-id', 'c', InputOption::VALUE_OPTIONAL, 'Content ID') ->addOption('location-id', 'l', InputOption::VALUE_OPTIONAL, 'Location ID') ->addOption('view-type', 't', InputOption::VALUE_OPTIONAL, 'View Type', 'line'); } protected function execute(InputInterface $input, OutputInterface $output): int { $contentId = $input->getOption('content-id'); $locationId = $input->getOption('location-id'); if (empty($contentId) && empty($locationId)) { throw new \InvalidArgumentException('No Content ID nor Location ID given'); } $viewParameters = [ 'viewType' => $input->getOption('view-type'), '_controller' => 'ibexa_content::viewAction', ]; if (!empty($locationId)) { $viewParameters['locationId'] = $locationId; } if (!empty($contentId)) { $viewParameters['contentId'] = $contentId; } // build view $contentView = $this->contentViewBuilder->buildView($viewParameters); // render view $renderedView = $this->templateRenderer->render($contentView); $output->writeln($renderedView); return 0; } } ``` > **Caution: Caution** > > As `Ibexa\Core\MVC\Symfony\View\Builder\ContentViewBuilder` and `Ibexa\Core\MVC\Symfony\View\Renderer\TemplateRenderer` aren't part of the public PHP API's `Ibexa\Contracts` namespace, they might change without notice. Use the command with some views: ``` php bin/console app:view --content-id=52 php bin/console app:view --location-id=2 --view-type=embed ``` # Templates You can customize the layout and look of your website with templates. Templates use the Twig template engine. > **Tip: Tip** > > Learn more about Twig templates from [Twig documentation](https://twig.symfony.com/doc/3.x/templates.html). ## Connecting templates Templates can inherit from other templates. Use this, for example, to inherit a general page layout including a [navigation menu](https://doc.ibexa.co/en/latest/templating/layout/add_navigation_menu/index.md) in article templates. To inherit from other templates, a template must extend the parent templates by using the [`extends()`](https://twig.symfony.com/doc/3.x/tags/extends.html) Twig function. To extend a parent template, the child template must contain Twig blocks. These blocks are inserted in the parent template in relevant places. For example, to extend the [general layout of the page](https://doc.ibexa.co/en/latest/templating/templates/template_configuration/#view-rules-and-matching), which includes, for example, header, footer, or navigation, in the child template place the content in a `content` block: ``` {% extends '@ibexadesign/pagelayout.html.twig' %} {% block content %} {% endblock %} ``` The parent template (in this case, `pagelayout.html.twig`) must leave a place for this block: ``` {% block content %} {% endblock %} ``` ## Template variables In templates, you can use variables related to the current content item, and general variables related to the current view and general application settings. > **Tip: Tip** > > For development purposes, you can list all available variables, or a single variable, and their values, by using the `dump()` Twig function: > > ``` > {{ dump() }} > {{ dump(content) }} > ``` Main variables include: | Variable | Description | | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | `content` | Content item, containing all Fields and version information (VersionInfo). | | `location` | Location object. Contains meta information on the Content (ContentInfo). | | `ibexa.siteaccess` | Current [SiteAccess](https://doc.ibexa.co/en/latest/multisite/multisite/index.md). | | `ibexa.rootLocation` | Root Location object. | | `ibexa.requestedUriString` | Requested URI string. | | `ibexa.systemUriString` | System URI string. System URI is the URI for internal content controller. If the current route isn't a URL alias, then the current PathInfo is returned. | | `ibexa.viewParameters` | View parameters as a hash. | | `ibexa.viewParametersString` | View parameters as a string. | | `ibexa.translationSiteAccess` | Translation SiteAccess for a given language (null if the SiteAccess cannot be found). | | `ibexa.availableLanguages` | List of available languages. | | `ibexa.configResolver` | [Config resolver](https://doc.ibexa.co/en/latest/administration/configuration/dynamic_configuration/#configresolver). | ### Custom template variables You can create custom Twig variables for use in templates. Set the variables per SiteAccess or SiteAccess group ([scope](https://doc.ibexa.co/en/latest/multisite/multisite_configuration/#scope)), or per content view. To configure a custom template variable per scope, use the `twig_variables` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa: system: site_group: twig_variables: custom_variable: 'variable_value' ``` You can access this variable directly in all templates in that scope: ``` {{ custom_variable }} ``` Variables set for a specific content view (under `params`) are only available when this view is matched: ``` line: article: template: '@ibexadesign/line/article.html.twig' match: Identifier\ContentType: [article] params: custom_variable_per_view: 'variable_value' ``` Custom variables can be nested: ``` twig_variables: custom_variable: nested_variable: 'variable_value' ``` ``` {{ custom_variable.nested_variable }} ``` You can use [Symfony Expression language](https://symfony.com/doc/7.4/components/expression_language.html) to access other values, for example: ``` params: custom_variable: "@=content.contentType.identifier" ``` > **Note: Note** > > A custom variable can overwrite an existing variable, so it's good practice to avoid existing variable names such as `content` or `location`. # Template configuration You configure how templates are used under the `ibexa.system..content_view` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files). The following example configuration defines template usage for several cases: ``` ibexa: system: site_group: page_layout: '@ibexadesign/pagelayout.html.twig' content_view: full: article: template: '@ibexadesign/full/article.html.twig' match: Identifier\ContentType: article blog_post: template: '@ibexadesign/full/blog_post.html.twig' controller: App\Controller\BlogController::showBlogPostAction match: Identifier\ContentType: [blog_post] terms: template: '@ibexadesign/full/terms_and_conditions.html.twig' match: Id\Content: 144 line: article: template: '@ibexadesign/line/article.html.twig' match: Identifier\ContentType: [article] ``` ## Scope The content view configuration must be placed under `ibexa.system.`. Scope defines the [SiteAccesses](https://doc.ibexa.co/en/latest/multisite/multisite/index.md) for which the configuration is valid. It may be a SiteAccess, a SiteAccess group, or one of the [generic configuration scopes](https://doc.ibexa.co/en/latest/multisite/multisite_configuration/#scope). ## Page layout `page_layout` defines the general layout of the whole site. Other templates can [extend the page layout](#page-layout). ``` page_layout: '@ibexadesign/pagelayout.html.twig' ``` ## View types The `ibexa.system..content_view` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files) defines rules for rendering content. Rules are grouped per *view type*. ``` ibexa: system: site_group: page_layout: '@ibexadesign/pagelayout.html.twig' content_view: full: ``` The default, built-in views are: - `full` - used when the content item is displayed by itself, as a full page - `line` - used when content is displayed as an item in a list, for example a list of the contents of a folder - `text_linked` - used for a text section which is a link - `embed` - used when one content item is embedded in another, as a block - `embed-inline` - used when a content item is embedded inline in another - `asset_image` - used when an image asset is embedded in another content item The built-in views have built-in default templates. You can define any other custom views. For each custom view, you must define a custom template. > **Tip: Direct path to previewing view types** > > You can preview content in a specific view type by using a direct path to the built-in view controller: > > `/view/content///true/` > > For example: > > `/view/content/55/embed/true/57` ## View rules and matching Each rule must have a name unique per view type. For each rule you must define the matching conditions. The `match` key can contain one or more [view matchers](https://doc.ibexa.co/en/latest/templating/templates/view_matcher_reference/index.md), including [custom ones](https://doc.ibexa.co/en/latest/templating/templates/create_custom_view_matcher/index.md). ``` blog_post: template: '@ibexadesign/full/blog_post.html.twig' controller: App\Controller\BlogController::showBlogPostAction match: Identifier\ContentType: [blog_post] ``` `template` indicates which template to use. `controller` indicates which [controller](https://doc.ibexa.co/en/latest/templating/queries_and_controllers/controllers/index.md) and which method to use when rendering the content. You can use it together with the `template` key, or without it. `params` can provide additional parameters to the content view. Use them, for example, with [Query types](https://doc.ibexa.co/en/latest/templating/queries_and_controllers/content_queries/#query-types) or to provide [custom Twig variables](https://doc.ibexa.co/en/latest/templating/templates/templates/#custom-template-variables) to the template. ### Combining matchers When you use more than one matcher in one rule, both conditions must match for the rule to apply. ``` match: Identifier\ContentType: [article, blog_post] Identifier\Section: news ``` In the example above, content which is either an article or a blog post is matched, but it must be in the "News" Section. ### Matching every content item When you use no matcher in a rule, this rule always match. Several values are available to declare no matcher: ``` match: ~ match: true match: [] ``` Such rules can be found in the [default template configuration](https://github.com/ibexa/core/blob/4.5/src/bundle/Core/Resources/config/default_settings.yml#L47). > **Tip: Tip** > > For example, you can ensure that any content item lacking a dedicated template isn't displayed in `full` view but is instead sent to a custom controller. > > ``` > site_group: > content_view: > full: > # Rules for content types and specific content items meant to be displayed in full view: > # … > # Rule for other content items not meant to be displayed in full view: > no_full_view: > controller: App\Controller\ViewController::noFullViewAction > template: '@ibexadesign/full/no_full_view.html.twig' > match: ~ > ``` > > This custom controller can also set the response status code to 404 using the following code: `$view->setResponse((new Response())->setStatusCode(404));`, and fetch reverse relations to provide suggestions on the error page. # View matcher reference You can use the following matchers to [match content views](https://doc.ibexa.co/en/latest/templating/templates/template_configuration/#view-rules-and-matching): | Identifier | Matches | | -------------------------------------------------------------- | ------------------------------------------------------------------------------------ | | [`Id\Content`](#idcontent) | ID number of the content item. | | [`Id\ContentType`](#idcontenttype) | ID number of the content type that the content item belongs to. | | [`Identifier\ContentType`](#identifiercontenttype) | Identifier of the content type that the content item belongs to. | | [`Id\ContentTypeGroup`](#idcontenttypegroup) | ID number of the group containing the content type that the content item belongs to. | | [`Id\Location`](#idlocation) | ID number of a Location. | | [`Id\LocationRemote`](#idlocationremote) | Remote ID number of a Location. | | [`Id\ParentContentType`](#idparentcontenttype) | ID number of the parent content type. | | [`Identifier\ParentContentType`](#identifierparentcontenttype) | Identifier of the parent content type. | | [`Id\ParentLocation`](#idparentlocation) | ID number of the parent Location. | | [`Id\Remote`](#idremote) | Remote ID of a content item. | | [`Id\Section`](#idsection) | ID number of the Section that the content item belongs to. | | [`Identifier\Section`](#identifiersection) | Identifier of the Section that the content item belongs to. | | [`Depth`](#depth) | Depth of the Location. The depth of a top level Location is 1. | | [`UrlAlias`](#urlalias) | Virtual URL of the Location. | | [Product attribute value](#product-attribute-value) | Value of product attributes. | | [Product code](#product-code) | Product code. | | [Product type](#product-type) | Product type. | | [Product availability](#product-availability) | Product availability. | | [Product](#product) | Whether the object is a product. | | [Product catalog root](#product-catalog-root) | Whether the Location is the root of a product catalog. | | [Taxonomy entry ID](#taxonomy-entry-id) | ID of taxonomy entry. | | [Taxonomy entry identifier](#taxonomy-entry-identifier) | Identifier of taxonomy entry. | | [Taxonomy entry level](#taxonomy-entry-level) | Level of taxonomy entry. | | [Taxonomy type](#taxonomy-type) | Taxonomy type. | > **Tip: Tip** > > Each matcher has a scalar value or an array of scalar values. When an array is passed, it matches on one of its values. > > You can also create [custom view matchers](https://doc.ibexa.co/en/latest/templating/templates/create_custom_view_matcher/index.md). ## Id\\Content Matches the ID number of a content item. ``` match: Id\Content: 145 ``` ## Id\\ContentType Matches the ID number of a content type that the content item belongs to. ``` match: Id\ContentType: 2 ``` ## Identifier\\ContentType Matches the identifier of the content type that the content item belongs to. ``` match: Identifier\ContentType: [blog_post] ``` ## Id\\ContentTypeGroup Matches the ID number of the content type Group that the content item belongs to. ``` match: Id\ContentTypeGroup: 1 ``` ## Id\\Location Matches the ID number of a location. In the case of a content item, matched against the main location. ``` match: Id\Location: 144 ``` ## Id\\LocationRemote Matches the Remote ID number of a location. In the case of a content item, matched against the main location. ``` match: Id\LocationRemote: 5b1e33529082b68ad3a41b9089136a0a ``` ## Id\\ParentContentType Matches the ID number of the parent content type. In the case of a content item, matched against the main location. ``` match: Id\ParentContentType: 42 ``` ## Identifier\\ParentContentType Matches the identifier of the parent content type. In the case of a content item, matched against the main location. ``` match: Identifier\ParentContentType: blog ``` ## Id\\ParentLocation Matches the ID number of the parent location. In the case of a content item, matched against the main location. ``` match: Id\ParentLocation: 2 ``` ## Id\\Remote Matches the remote ID number of a content item. ``` match: Id\Remote: 145 ``` ## Id\\Section Matches the ID number of the section that the content item belongs to. ``` match: Id\Section: 1 ``` ## Identifier\\Section Matches the identifier of the section that the content item belongs to. ``` match: Identifier\Section: standard ``` ## Depth Matches the depth of the location. The depth of a top level location is 1. ``` match: Depth: 2 ``` ## UrlAlias Matches the virtual URL of the location. Matches when the URL alias of the location starts with the value passed. ``` match: UrlAlias: 'terms-and-conditions' ``` ## Product attribute value `Ibexa\Contracts\ProductCatalog\ViewMatcher\ProductBased\AttributeValue` matches the value of product attributes. ``` match: '@Ibexa\Contracts\ProductCatalog\ViewMatcher\ProductBased\AttributeValue': { width: 20, height: 10 } ``` ## Product code `Ibexa\Contracts\ProductCatalog\ViewMatcher\ProductBased\ProductCode` matches the product code. ``` match: '@Ibexa\Contracts\ProductCatalog\ViewMatcher\ProductBased\ProductCode': ['DRE1536SF'] ``` ## Product type `Ibexa\Contracts\ProductCatalog\ViewMatcher\ProductBased\ProductType` matches the product type. ``` match: '@Ibexa\Contracts\ProductCatalog\ViewMatcher\ProductBased\ProductType': ['dress'] ``` ## Product availability `Ibexa\Contracts\ProductCatalog\ViewMatcher\ProductBased\IsAvailable` matches the availability of a product. Refers to the existence of availability, not to whether the product is in stock. ``` match: '@Ibexa\Contracts\ProductCatalog\ViewMatcher\ProductBased\IsAvailable': true ``` ## Product `Ibexa\Contracts\ProductCatalog\ViewMatcher\ProductBased\IsProduct` matches when the object is a product. ``` match: '@Ibexa\Contracts\ProductCatalog\ViewMatcher\ProductBased\IsProduct': ~ ``` ## Product catalog root `Ibexa\Contracts\ProductCatalog\ViewMatcher\LocationBased\RootLocation` matches depending on whether the location is the root of a product catalog. ``` match: '@Ibexa\Contracts\ProductCatalog\ViewMatcher\LocationBased\RootLocation': true ``` ## Taxonomy entry ID `Ibexa\Taxonomy\View\Matcher\TaxonomyEntryBased\Id` matches based on an ID of the taxonomy entry. ``` match: '@Ibexa\Taxonomy\View\Matcher\TaxonomyEntryBased\Id': [1, 2, 3]' ``` ## Taxonomy entry identifier `Ibexa\Taxonomy\View\Matcher\TaxonomyEntryBased\Identifier` matches based on an identifier of the taxonomy entry. ``` match: '@Ibexa\Taxonomy\View\Matcher\TaxonomyEntryBased\Identifier': ['spring', 'events', 'devices'] ``` ## Taxonomy entry level `Ibexa\Taxonomy\View\Matcher\TaxonomyEntryBased\Level` matches based on a level of the taxonomy entry. With this matcher, you can apply view rules based on a selection of taxonomy entry levels, by using the following logical operators: `<` , `>` , `<=`, `>=`, `=`. ``` match: '@@Ibexa\Taxonomy\View\Matcher\TaxonomyEntryBased\Level': '> 2' ``` ## Taxonomy type `Ibexa\Taxonomy\View\Matcher\TaxonomyEntryBased\Taxonomy` matches based on a type of taxonomy that the taxonomy entry belongs to. ``` match: '@Ibexa\Taxonomy\View\Matcher\TaxonomyEntryBased\Taxonomy': 'product_category' ``` # Create custom view matcher In addition to the [built-in view matchers](https://doc.ibexa.co/en/latest/templating/templates/view_matcher_reference/index.md), you can also create custom matchers to use in [template configuration](https://doc.ibexa.co/en/latest/templating/templates/template_configuration/#view-rules-and-matching). To do it, create a matcher class that implements `Ibexa\Core\MVC\Symfony\Matcher\ContentBased\MatcherInterface`. ## Matcher class The matcher class must implement the following methods: - `matchLocation` - checks if a location object matches. - `matchContentInfo` - checks if a ContentInfo object matches. - `match` - checks if the View object matches. - `setMatchingConfig` - receives the matcher's config from the view rule. The following example shows how to implement an `Owner` matcher. This matcher identifies content items that have the provided owner or owners. ``` hasOwner($location->getContentInfo()); } /** * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException */ public function matchContentInfo(ContentInfo $contentInfo): bool { return $this->hasOwner($contentInfo); } /** * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException */ public function match(View $view): ?bool { if ($view instanceof LocationValueView) { return $this->matchLocation($view->getLocation()); } if ($view instanceof ContentValueView) { return $this->matchContentInfo($view->getContent()->contentInfo); } return false; } /** * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException */ private function hasOwner(ContentInfo $contentInfo): bool { $owner = $this->userService->loadUser($contentInfo->ownerId); return in_array($owner->login, $this->matchingUserLogins, true); } /** * @param array $matchingConfig */ public function setMatchingConfig($matchingConfig): void { if (!is_array($matchingConfig)) { throw new InvalidArgumentException('App\Owner view matcher configuration has to be an array'); } $this->matchingUserLogins = $matchingConfig; } } ``` The matcher checks whether the owner of the current content (by its ContentInfo or location) matches any of the values passed in configuration. ## Matcher service You configure your matcher as a service, tag it `ibexa.view.matcher`, and associate it with the identifier to use in view rules: ``` services: App\View\Matcher\Owner: autowire: true tags: - { name: ibexa.view.matcher, identifier: App\Owner } ``` ## View configuration To apply the matcher in view configuration, indicate the matcher by its identifier. The following configuration uses a special template to render articles owned by the users with provided logins: ``` ibexa_design_engine: design_list: my_design: [ my_theme ] ibexa: system: site_group: design: my_design content_view: full: editor_articles: template: '@ibexadesign/full/featured_article.html.twig' match: Identifier\ContentType: article App\Owner: [johndoe, janedoe] ``` > **Note: Note** > > If you use a matcher that is a service instead of a simple class, tag the service with `ibexa.view.matcher`. # Assets Assets enable you to add CSS, JS, image, or other files to your project, to style and customize its look and behavior. ## Asset files To add assets to the project, provide asset files (such as CSS or JS files) in the `assets` folder, for example under `assets/css` and `assets/js`. ## Configure assets All asset files must be added to `webpack.config.js` in the root folder, so that Webpack Encore can use them. To do it, use `Encore.addStyleEntry` for CSS files and `Encore.addEntry` for other files, such as JS: ``` Encore.addStyleEntry('style', [ path.resolve(__dirname, './assets/css/style.css'), ]); Encore.addEntry('script', [ path.resolve(__dirname, './assets/js/script.js'), ]); ``` ## Include assets in templates To include assets in your templates, add them to the template's `` tag, and provide the name of the asset entry you configured in `webpack.config.js`, for example: ``` {{ encore_entry_link_tags('style') }} {{ encore_entry_script_tags('script') }} ``` > **Note: Note** > > After you add the asset files, clear the cache and run `yarn encore `. To include a single asset file in your template, for example an image, use the [`asset()`](https://symfony.com/doc/7.4/reference/twig_reference.html#asset) Twig function: ``` ``` Place the image file in the `public/assets/images` folder. # Image variations With image variations you can render different versions of one image by means of scaling, cropping and other filters. Built-in image variations include four versions that provide the image at a specific scale: `tiny`, `small`, `medium`, and `large`. You can also create custom image variations. See [Render images](https://doc.ibexa.co/en/latest/templating/embed_and_list_content/render_images/index.md) for an example of variation name usage as `alias` parameter when rendering an image field. ## Custom image variations Image variation configuration is [SiteAccess](https://doc.ibexa.co/en/latest/multisite/multisite/index.md)-aware. Place it under the `image_variations` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files) per [scope](https://doc.ibexa.co/en/latest/multisite/multisite_configuration/#scope): ``` ibexa: system: : image_variations: : reference: null filters: : ``` Variation name must be unique. It may contain characters, numbers, underscores (`_`) or hyphens (`-`), but no spaces. Each variation takes the following parameters: - `reference` - (optional) name of a reference variation to base the variation on. If set to `null` or `~`, the variation takes the original image for reference. - `filters` - array of variation filters and their parameters. - `post_processors` - used to reduce the final image size and to improve load performance of assets. ## Available variation filters In addition to [filters exposed by LiipImagineBundle](https://symfony.com/bundles/LiipImagineBundle/2.x/filters.html), the following ones are available: | Filter name | Parameters | Description | | ------------------------------ | ------------------------------------------ | -------------------------------------------------------------------------------------------------- | | `geometry/scaledownonly` | `[width, height]` | Scales image down to fit the provided width/height. Preserves aspect ratio. | | `geometry/scalewidthdownonly` | `[width]` | Scales image down to fit the provided width. Preserves aspect ratio. | | `geometry/scaleheightdownonly` | `[height]` | Scales image down to fit the provided height. Preserves aspect ratio. | | `geometry/scalewidth` | `[width]` | Scales image width, both up and down. Preserves aspect ratio. | | `geometry/scaleheight` | `[height]` | Scales image height, both up and down. Preserves aspect ratio. | | `geometry/scale` | `[width, height]` | Scales image size to the provided width and height, both up and down. Preserves aspect ratio. | | `geometry/scaleexact` | `[width, height]` | Scales image to exactly fit the provided width and height. Doesn't preserve aspect ratio. | | `geometry/scalepercent` | `[widthPercent, heightPercent]` | Scales width and height by the provided percent values. Doesn't preserve aspect ratio. | | `geometry/crop` | `[width, height, startX, startY]` | Crops the image. The result has the provided width/height, starting at the provided startX/startY | | `border` | `[thickBorderX, thickBorderY, color=#000]` | Adds a border around the image. Thickness is defined in px. Color is `#000` by default. | | `filter/noise` | `[radius=0]` | Smooths the contours of an image (`imagick`/`gmagick` only). `radius` is in px. | | `filter/swirl` | `[degrees=60]` | Swirls the pixels of the center of the image (`imagick`/`gmagick` only). `degrees` defaults to 60. | | `resize` | {size: `[width, height]`} | Resize filter (provided by LiipImagineBundle). | | `colorspace/gray` | N/A | Converts the image to grayscale. | > **Note: Note** > > After you change the image variation configuration, remove the existing variations with the `liip:imagine:cache:remove` command and provide the variation name: > > ``` > php bin/console liip:imagine:cache:remove --filter=large > ``` > > Next, clear the cache. > > You can also remove all generated image variations: > > ``` > php bin/console liip:imagine:cache:remove -v > ``` # Twig function reference In addition to the [native functions provided by Twig](https://twig.symfony.com/doc/3.x/functions/index.html), and [Twig extensions provided by Symfony](https://symfony.com/doc/7.4/reference/twig_reference.html), Ibexa DXP offers the following custom Twig functions and filters: - [Cart Twig functions](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/templating/twig_function_reference/cart_twig_functions/): Cart Twig functions enable checking whether product can be added to cart and formatting the price. - [Catalog Twig functions](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/templating/twig_function_reference/catalog_twig_functions/): Catalog Twig functions enable getting and rendering certain catalog information. - [Checkout Twig functions](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/templating/twig_function_reference/checkout_twig_functions/): Checkout Twig functions return information about the checkout process, and total values related to cart and cart items. - [Content Twig functions](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/templating/twig_function_reference/content_twig_functions/): Content Twig function enable rendering whole content items and their information. - [Twig Component functions](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/templating/twig_function_reference/component_twig_functions/): Twig Components allow you to inject custom widgets into selected templates - [Field Twig functions](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/templating/twig_function_reference/field_twig_functions/): Field Twig function enable rendering content fields, their values and their information. - [Page Twig functions](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/templating/twig_function_reference/page_twig_functions/): Page field and page block Twig functions provide access to configuration. - [Product Twig functions](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/templating/twig_function_reference/product_twig_functions/): Product Twig functions enable getting products and their attributes in templates. - [Recommendations Twig functions](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/templating/twig_function_reference/recommendations_twig_functions/): Recommendations Twig Functions - [Site context Twig functions](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/templating/twig_function_reference/site_context_twig_functions/): Site context Twig function determines if given location is site context-aware. - [Storefront Twig functions](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/templating/twig_function_reference/storefront_twig_functions/): Storefront Twig function enable controlling the rendering of storefront elements. - [Icon Twig functions](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/templating/twig_function_reference/icon_twig_functions/): Icon Twig functions enable referencing SVG icons in templates. - [Image Twig functions](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/templating/twig_function_reference/image_twig_functions/): Image Twig functions enable rendering images in a specific variation. - [URL Twig functions](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/templating/twig_function_reference/url_twig_functions/): URL Twig functions enable rendering URLs and routes. - [Date Twig filters](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/templating/twig_function_reference/date_twig_filters/): Use date Twig filters to select the date and time format used in templates. - [AI Actions Twig functions](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/templating/twig_function_reference/ai_actions_twig_functions/): AI Actions functions allows you to embed AI Actions in your templates - [Discounts Twig functions](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/templating/twig_function_reference/discounts_twig_functions/): Discounts Twig Functions allow you to operate on discounts in your templates. - [Quable Twig functions](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/templating/twig_function_reference/quable_twig_functions/): Twig functions exposed by the Quable connector. # AI Actions Twig functions AI Actions functions allows you to pass information about AI Actions to the Twig templates. ### `ibexa_ai_config()` The `ibexa_ai_config` function loads the configuration of an Action Type with given identifier. ``` {% set ai_config = ibexa_ai_config('refine_text') %} {% set ai_config2 = ibexa_ai_config('refine_text', { 'some': 'optional data' }) %} ``` You can use the [ResolveActionConfigurationWidgetConfigEvent](https://doc.ibexa.co/en/latest/api/event_reference/ai_action_events/#others) event to modify the configuration after it's been loaded. The optional data provided in the function call is available in the event object. # Cart Twig functions Editions: Commerce You can use cart Twig functions to check whether products can be added to cart, or to format the price value. ### `ibexa_can_be_added_to_cart()` The `ibexa_can_be_added_to_cart()` function checks whether the provided product can be added to cart. It eliminates products that aren't available, products that don't have a price that corresponds to a currency selected for the cart, and products, for which VAT category isn't set. It also eliminates products that have variants but aren't one of those variants. ``` {% set is_disabled = (is_disabled or ibexa_can_be_added_to_cart(product) == false)|default(false) %} ``` # Catalog Twig functions With the catalog Twig functions you can get catalog location and render catalog status. ### `ibexa_get_product_catalog_root` The `ibexa_get_product_catalog_root()` function gets a root location of the product catalog (configured in `ibexa_product_catalog.engines.default.type.options.root_location_remote_id`). ``` {{ ibexa_url(ibexa_get_product_catalog_root()) }} ``` ### `ibexa_render_catalog_status` The `ibexa_render_catalog_status` filter renders the status of the catalog, translated into the current language. #### Examples ``` {% import "@ibexadesign/product_catalog/catalog_macros.html.twig" as catalog_macros %} {{ catalog_macros.status_node(catalog.status) }} ``` # Checkout Twig functions Editions: Commerce You can use checkout Twig functions to get information about the checkout process, and total values related to cart and cart items. ### `ibexa_checkout_step_label()` The `ibexa_checkout_step_label()` function returns a name of the step (configured in `framework.workflows.workflow.ibexa_checkout.transitions..metadata.label`). ``` {% block title %}

    {{ ibexa_checkout_step_label(checkout, step) }}

    {% endblock %} ``` ### `ibexa_checkout_steps()` The `ibexa_checkout_steps()` function returns a list of steps configured in `framework.workflows.workflow.ibexa_checkout.transitions`). ``` {% for step in ibexa_checkout_steps(checkout) %} // ... {% endfor %} ``` ### `ibexa_checkout_step_path()` The `ibexa_checkout_step_path()` function returns a path to the step. ``` {{ }} ``` ### `ibexa_checkout_step_url()` The `ibexa_checkout_step_url()` function returns a URL address of the step. By setting the optional argument to `true` you can decide whether the function returns a relative or absolute URL of the checkout step. The default value of the optional argument is `false`, which stands for the absolute URL. ``` {{ }} ``` ### `ibexa_checkout_step_number()` The `ibexa_checkout_step_number` function returns a sequential number of the step (based on configuration under `framework.workflows.workflow.ibexa_checkout.transitions`). ``` {% block page_number %}

    {{ ibexa_checkout_step_number(checkout, step) }}

    {% endblock %} ``` ### `ibexa_checkout_summary_entries()` The `ibexa_checkout_summary_entries` function takes in a single argument, a cart summary object, and returns the checkout summary. ``` {% block items %} {% for entry in ibexa_checkout_summary_entries(summary) %} // ... {% endfor %} {% endblock %} ``` ### `ibexa_checkout_summary_vat_summaries()` The `ibexa_checkout_summary_vat_summaries()` function takes in a single argument, a cart summary object, and returns an array of VAT summary objects for the cart. Each VAT summary relates to a certain VAT rate, and contains information about the VAT rate, and the VAT value. ``` {% set vat_summaries = ibexa_checkout_summary_vat_summaries(summary) %} ``` # Twig Component functions ## `ibexa_twig_component_group` The `ibexa_twig_component_group` function renders the specified [Twig Component](https://doc.ibexa.co/en/latest/templating/components/index.md) group. You can pass optional parameters as the second argument. They are passed to the `\Ibexa\Contracts\TwigComponents\Renderer\RendererInterface` service and then to the rendered component. ### Examples ``` {{ ibexa_twig_component_group('storefront-before-maincart', { 'cart' : cart|default(null) }) }} ``` ## `ibexa_twig_component` The `ibexa_twig_component` function renders a single Twig Component from the specified group. You can pass optional parameters as the third argument. They are passed to the `\Ibexa\Contracts\TwigComponents\Renderer\RendererInterface` service and then to the rendered component. ### Examples ``` {{ ibexa_twig_component('storefront-before-maincart', 'my-component', { 'cart' : cart|default(null) }) }} ``` # Content Twig functions - [`ibexa_render()`](#ibexa_render) renders a content item. - [`ibexa_content_name()`](#ibexa_content_name) renders the name of a content item. - [`ibexa_render_content_query()`](#ibexa_render_content_query) renders the results of a non-content related query. - [`ibexa_render_location_query()`](#ibexa_render_location_query) renders the results of a non-content related Location query. - [`ibexa_seo_is_empty()`](#ibexa_seo_is_empty) returns a Boolean indication of whether SEO data is available for a content item. - [`ibexa_seo()`](#ibexa_seo) attaches SEO tags to content item's HTML code. ## Content rendering ### `ibexa_render()` `ibexa_render()` renders the indicated content item. It uses the `embed` view by default, but you can pass a different view as an argument. You can provide `ibexa_render()` with either a content item or a Location object. > **Tip: Tip** > > Depending on whether you pass a content item or a Location object, the helper automatically selects and uses one of internal Twig functions: `ibexa_render_content()` or `ibexa_render_location()`. | Argument | Type | Description | | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `content` or `location` | [`Content`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Content.html), [`ContentAwareInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-ContentAwareInterface.html) or [`Location`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Location.html) | Content item or its location. | | `method` | `string` | (optional) [Rendering method](#rendering-methods). One of: `direct`, `inline`, `esi`, `ssi`. (Default method is `direct`) | | `viewType` | `string` | (optional) [View type](https://doc.ibexa.co/en/latest/templating/templates/template_configuration/#view-types). (Default view type is `embed`) | | `params` | `array` | (optional) Hash of variables to pass to the template. | #### Rendering methods You can pass one of the following rendering methods to `ibexa_render()`: - `direct` - (default) renders the content item without using a request - `inline` - Symfony inline rendering method, sends a request to the server and inserts the response - `esi` - uses the Symfony [Edge Side Include mechanism](https://symfony.com/doc/7.4/http_cache/esi.html) to render the correct tag that is handled by the reverse proxy - `ssi` - uses the Symfony [Server Side Include mechanism](https://symfony.com/doc/7.4/http_cache/ssi.html) to render the correct tag that is handled by the web server ``` {{ ibexa_render(location) }} {{ ibexa_render(content, {'viewType': 'line'}) }} {{ ibexa_render(content, {'method': 'inline'}) }} {{ ibexa_render(content, { 'viewType': 'line', 'params': { 'custom_param': 'custom_value' } }) }} ``` ## Content information ### `ibexa_content_name()` `ibexa_content_name()` renders the name of a content item. The function uses prioritized languages from SiteAccess settings unless you pass another language as `forcedLanguage`. If the content item doesn't have a translation in the prioritized or passed language, the function returns the name in the main language. | Argument | Type | Description | | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | | `content` | [`Content`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Content.html), [`ContentInfo`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-ContentInfo.html), or [`ContentAwareInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-ContentAwareInterface.html) | Content item, its ContentInfo object, or ContentAwareInterface object. | | `forcedLanguage` | `string` | (optional) Language to use (for example, `fre-FR`). | ``` {{ ibexa_content_name(content) }} {{ ibexa_content_name(content, 'pol-PL') }} ``` ``` {{ ibexa_content_name(product) }} {{ ibexa_content_name(product, 'fr-FR') }} ``` ### `ibexa_seo_is_empty()` `ibexa_seo_is_empty()` returns a Boolean value which indicates whether [SEO](https://doc.ibexa.co/projects/userguide/en/5.0/search_engine_optimization/seo/) data is available for the content item that is passed as an argument. | Argument | Type | Description | | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | | `content` | [`Content`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Content.html) or [`ContentAwareInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-ContentAwareInterface.html) | Content item or ContentAwareInterface object. | ``` {{ ibexa_seo_is_empty(content) }} ``` ``` {{ ibexa_seo_is_empty(product) }} ``` ### `ibexa_seo()` `ibexa_seo()` attaches [SEO](https://doc.ibexa.co/projects/userguide/en/5.0/search_engine_optimization/seo/) data to the content item's HTML code. | Argument | Type | Description | | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | | `content` | [`Content`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Content.html) or [`ContentAwareInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-ContentAwareInterface.html) | Content item or ContentAwareInterface object. | ``` {{ ibexa_seo(content) }} ``` ``` {{ ibexa_seo(product) }} ``` > **Tip: Tip** > > The following example uses both SEO-related functions: > > ``` > {% if not ibexa_seo_is_empty(content) %} > {{ ibexa_seo(content)}} > {% else %} > {{ ibexa_content_name(content) }} > # Generate other tags > {% endif %} > ``` ### `ibexa_taxonomy_entries_for_content()` filter `ibexa_taxonomy_entries_for_content()` fetches names of content categories. | Argument | Type | Description | | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | | `content` | [`Content`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Content.html) or [`ContentAwareInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-ContentAwareInterface.html) | Content item to display the category name for. | ``` {{ content|ibexa_taxonomy_entries_for_content|map(entry => "#{entry.name}")|join(', ') }} ``` ``` {{ product|ibexa_taxonomy_entries_for_content|map(entry => "#{entry.name}")|join(', ') }} ``` ## Non-content related queries ### `ibexa_render_content_query()` `ibexa_render_content_query` renders the results of a non-content related query made by using a Query type. | Argument | Type | Description | | --------- | ----- | --------------------------------------------------------- | | `options` | array | Available options are: `query`, `pagination`, `template`. | > **Tip: Tip** > > For an example of using `ibexa_render_content_query`, see [Add navigation menu](https://doc.ibexa.co/en/latest/templating/layout/add_navigation_menu/#render-menu-using-a-query). ### `ibexa_render_location_query()` `ibexa_render_location_query` renders the results of a non-content related location query made by using a Query type. | Argument | Type | Description | | --------- | ----- | --------------------------------------------------------- | | `options` | array | Available options are: `query`, `pagination`, `template`. | # Date Twig filters Date and time Twig filters format a date and time object (`DateTimeInterface`) in one of the formats defined in [user preferences](https://doc.ibexa.co/en/latest/administration/back_office/back_office_elements/formatting_date_and_time/#within-user-settings-menu). - `ibexa_full_datetime` - `ibexa_full_date` - `ibexa_full_time` - `ibexa_short_datetime` - `ibexa_short_date` - `ibexa_short_time` If the `DateTimeInterface` argument is null, the filter returns the current date and time in the selected format. ``` {{ content.contentInfo.publishedDate|ibexa_full_datetime }} ``` The filters also accept an optional `timezone` parameter for displaying date and time in a chosen time zone: ``` {{ content.contentInfo.publishedDate|ibexa_short_datetime('PST') }} ``` ## Considerations for use outside the back office The filters rely on user preferences. When the preferences are not set, for example, for logged out users, the filters fall back to a default date format. For some filters, the fallback date format includes locale-aware fragments, such as the full month or day name. When combined with [reverse proxies like Varnish or Fastly](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/http_cache/http_cache/index.md), it's possible to cache a localized version of a date and display it to other users, even if they're not using the same locale. Consider these alternatives: - Use Twig's built-in `date` filter with a fixed, locale-independent format ``` {{ content.contentInfo.publishedDate|date('Y-m-d H:i:s') }} ``` - Use ESI for dynamic rendering of the date ``` {{ render_esi(controller('App\\Controller\\CustomDateController::format', { 'date': content.contentInfo.publishedDate })) }} ``` Don't cache the ESI response. By implementing this solution, you can keep the date format locale-aware. - Client-side JavaScript formatting ``` {{ content.contentInfo.publishedDate|date('Y-m-d H:i:s') }} ``` For more information, see [HTTP Cache](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/http_cache/http_cache/index.md) and [Delivering personalized responses](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/http_cache/context_aware_cache/#personalize-responses). # Discounts Twig functions Editions: Commerce Discounts Twig Functions allow you to operate on discounts in your templates. ## Filters ### `ibexa_render_discount_rule_type` This filter transforms the discount type (`fixed_amount` or `percentage`) into a human-friendly and translated label. ``` {% set rule_type = discount.getRule().getType() %} {{ rule_type|ibexa_render_discount_rule_type }} ``` ## Functions ### `ibexa_discounts_render_discount_badge()` Use the `ibexa_discounts_render_discount_badge` to render a badge indicating the discounted amount, for example on product cards. ``` {% if ibexa_storefront_are_discounts_enabled() %} {% block product_discount_price_info %}
    {% include '@ibexadesign/storefront/component/discount/discount_price.html.twig' with { original_price: original_price, } %} {% embed '@ibexadesign/storefront/component/discount/discount_badge.html.twig' with { size: 'small', } %} {% block content %} {{- ibexa_discounts_render_discount_badge(discount, price_money) -}} {% endblock %} {% endembed %}
    {% endblock %} {% endif %} ``` ### `ibexa_get_original_price()` Displays the product price before the discount was applied. ``` {{ ibexa_get_original_price(product)|ibexa_format_price ?: '-' }} ``` ### `ibexa_format_discount_value()` Formats the discount value for each discount type, for example by displaying `-10 EUR` or `-10%`. ``` content: ibexa_format_discount_value(discount), ``` ### `ibexa_discounts_is_active()` Helper function returning whether the discount is currently active. ``` {% if ibexa_discounts_is_active(discount) %}
    The discount is active
    {% endif %} ``` ### `ibexa_discounts_form_themes()` The `ibexa_discounts_form_themes` function serves as an extension point to provide new [form themes](https://symfony.com/doc/7.4/form/form_themes.html) for the discount form. To add new ones, create a class implementing the [FormThemeProviderInterface](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-Form-FormThemeProviderInterface.html) interface and provide them in the `getFormThemes` method. ### `ibexa_discounts_can_edit()` Helper function returning whether the current user has permissions to edit discounts. ### `ibexa_discounts_can_enable()` Helper function returning whether the current user has permissions to enable discounts. ### `ibexa_discounts_can_disable()` Helper function returning whether the current user has permissions to disable discounts. ### `ibexa_discounts_can_delete()` Helper function returning whether the current user has permissions to delete discounts. # Field Twig functions Field Twig functions render specific fields of a content item and provide information about them. - [`ibexa_render_field()`](#ibexa_render_field) renders the selected field of a content item. - [`ibexa_field_value()`](#ibexa_field_value) returns the field value object. - [`ibexa_field()`](#ibexa_field) returns the field object. `ibexa_field()` returns the *Field object*, and `ibexa_field_value()` returns the *Field's raw value*. `ibexa_render_field()` is the Twig function intended for rendering the field on the front page. You can get additional information about a field by using the following Twig functions: - [`ibexa_field_name()`](#ibexa_field_name) returns the name of a content item's field. - [`ibexa_field_description()`](#ibexa_field_description) returns the description of a content item's field. - [`ibexa_field_is_empty()`](#ibexa_field_is_empty) returns Boolean information whether a field of a content item is empty. - [`ibexa_field_group_name()`](#ibexa_field_group_name) returns a human-readable name of the field group. - [`ibexa_has_field()`](#ibexa_has_field) checks whether a field is present in the content item. ## Field rendering ### `ibexa_render_field()` `ibexa_render_field()` renders the selected field of a content item. The field is rendered with the default template, but you can optionally pass a different template as parameter as well. | Argument | Type | Description | | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | | `content` | [`Content`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Content.html) or [`ContentAwareInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-ContentAwareInterface.html) | Content item the field belongs to. | | `fieldDefinitionIdentifier` | `string` | Field identifier. | | `params` | `hash` | (optional) Hash of parameters passed to the template block. | ``` {{ ibexa_render_field(content, 'title') }} {{ ibexa_render_field(content, 'image', { 'template': '@ibexadesign/fields/image.html.twig', 'attr': {class: 'thumbnail-image'}, 'parameters': { 'alias': 'small' } }) }} ``` ``` {{ ibexa_render_field(product, 'name') }} {{ ibexa_render_field(product, 'image', { 'template': '@ibexadesign/fields/image.html.twig', 'attr': {class: 'thumbnail-image'}, 'parameters': { 'alias': 'small' } }) }} ``` #### Parameters You can pass the following parameters to `ibexa_render_field()`: - `lang` - language to render the field in (overrides the current language), must be a valid locale in xxx-YY format - `template` - field template to use - `attr` - hash of HTML attributes to add to the tag - parameters - arbitrary parameters to pass to the template block. Some field types, like the [MapLocation field type](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/maplocationfield/index.md), expect specific parameters. #### Examples ``` {{ ibexa_render_field(content, 'title', { 'attr': { class: 'article-title' } }) }} ``` ## Field values ### `ibexa_field_value()` `ibexa_field_value()` returns the field value object. The function returns the value of the field only. To render the field with default or custom templates, use [`ibexa_render_field()`](#ibexa_render_field) instead. If the content item doesn't have a translation in the prioritized or passed language, the function returns the value in the main language. | Argument | Type | Description | | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | | `content` | [`Content`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Content.html) or [`ContentAwareInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-ContentAwareInterface.html) | Content item the field belongs to. | | `fieldDefIdentifier` | `string` | Identifier of the field. | | `forcedLanguage` | `string` | (optional) Language to use (for example, "fre-FR"). | ``` {{ ibexa_field_value(content, 'image') }} ``` ``` {{ ibexa_field_value(product, 'image') }} ``` ### `ibexa_field()` `ibexa_field()` returns the field object. The field gives you access to the field value, the field's definition identifier, and field type identifier. If the content item doesn't have a translation in the prioritized or passed language, the function returns the field object in the main language. | Argument | Type | Description | | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | | `content` | [`Content`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Content.html) or [`ContentAwareInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-ContentAwareInterface.html) | Content item the field belongs to. | | `fieldDefIdentifier` | `string` | Identifier of the field. | | `forcedLanguage` | `string` | {optional) Language to use (for example, "fre-FR"). | You can access the field's value by using `(ibexa_field(content, 'my_field').value)`, but it's recommended to use the dedicated [`ibexa_field_value()`](#ibexa_field_value) function for this. You can use `ibexa_field()` to access the field type identifier: ``` {{ ibexa_field(content, 'my_field').fieldTypeIdentifier }} ``` ``` {{ ibexa_field(product, 'my_field').fieldTypeIdentifier }} ``` ## Field information ### `ibexa_field_name()` `ibexa_field_name()` returns the name of a content item's field. The function uses prioritized languages from SiteAccess settings unless you pass another language as `forcedLanguage`. If the content item doesn't have a translation in the prioritized or passed language, the function returns the name in the main language. | Argument | Type | Description | | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | | `content` | [`Content`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Content.html), [`ContentInfo`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-ContentInfo.html), or [`ContentAwareInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-ContentAwareInterface.html) | Content item the field belongs to. | | `fieldDefIdentifier` | `string` | Identifier of the field. | | `forcedLanguage` | `string` | (optional) Language to use (for example, `fre-FR`). | ``` {{ ibexa_field_name(content, 'title') }} {{ ibexa_field_name(content, 'title', 'ger-DE') }} ``` ``` {{ ibexa_field_name(product, 'name') }} {{ ibexa_field_name(product, 'name', 'pl-PL') }} ``` ### `ibexa_field_description()` `ibexa_field_description()` returns the description of a content item's field. The function uses prioritized languages from SiteAccess settings unless you pass another language as `forcedLanguage`. If the content item doesn't have a translation in the prioritized or passed language, the function returns the description in the main language. | Argument | Type | Description | | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | | `content` | [`Content`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Content.html), [`ContentInfo`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-ContentInfo.html), or [`ContentAwareInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-ContentAwareInterface.html) | Content item the field belongs to. | | `fieldDefIdentifier` | `string` | Identifier of the field. | | `forcedLanguage` | `string` | (optional) Language to use (for example, `fre-FR`). | ``` {{ ibexa_field_description(content, 'title') }} {{ ibexa_field_description(content, 'title', 'ger-DE') }} ``` ``` {{ ibexa_field_description(product, 'name') }} {{ ibexa_field_description(product, 'name', 'fr-FR') }} ``` ### `ibexa_field_is_empty()` `ibexa_field_is_empty()` returns Boolean information whether a given field of a content item is empty. | Argument | Type | Description | | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | | `content` | [`Content`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Content.html) or [`ContentAwareInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-ContentAwareInterface.html) | Content item the field belongs to. | | `fieldDefIdentifier` | `string` | Identifier of the field. | | `forcedLanguage` | `string` | (optional) Language to use (for example, `fre-FR`). | ``` {{ ibexa_field_is_empty(content, 'title') }} ``` ``` {{ ibexa_field_is_empty(product, 'name') }} ``` #### Examples For example, use `ibexa_field_is_empty()` to check whether a field is empty or filled before rendering it: ``` {% if not ibexa_field_is_empty(content, 'image') %} {{ ibexa_render_field(content, 'image') }} {% endif %} ``` ``` {% if not ibexa_field_is_empty(product, 'image') %} {{ ibexa_render_field(product, 'image') }} {% endif %} ``` ### `ibexa_field_group_name()` `ibexa_field_group_name()` returns a human-readable name of a field group. | Argument | Type | Description | | ---------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | `fieldGroupIdentifier` | `string` | Field group [identifier](https://doc.ibexa.co/en/latest/administration/configuration/repository_configuration/#field-groups-configuration). | ``` {{ ibexa_field_group_name('content') }} ``` ### `ibexa_has_field()` `ibexa_has_field()` returns Boolean information whether a field is present in the content item. | Argument | Type | Description | | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- | | `content` | [`Content`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Content.html) or [`ContentAwareInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-ContentAwareInterface.html) | Content item the field may belong to. | | `fieldDefIdentifier` | `string` | Identifier of the field. | ``` {% if ibexa_has_field(content, 'existing') %} {{ ibexa_render_field(content, 'existing') }} {% endif %} ``` ``` {% if ibexa_has_field(product, 'existing') %} {{ ibexa_render_field(product, 'existing') }} {% endif %} ``` # Icon Twig functions ## `ibexa_icon_path()` `ibexa_icon_path()` generates a path to the selected icon from an [icon set](https://doc.ibexa.co/en/latest/administration/back_office/back_office_elements/custom_icons/#icon-sets). | Argument | Type | Description | | -------- | -------- | ------------------------------------------------------------------------------ | | `icon` | `string` | Identifier of an icon in the icon set. | | `set` | `string` | Identifier of the configured icon set. If empty, the default icon set is used. | ``` ``` The icons can be displayed in different colors and sizes. ### Icon color variants By default, the icon inherits the [`fill`](https://developer.mozilla.org/en-US/docs/Web/CSS/fill) attribute from the parent element. You can change it by using one of the available CSS modifiers: | Modifier | Usage | | ------------- | -------------------------------------- | | `--light` | To be used on dark backgrounds | | `--dark` | To be used on light backgrounds | | `--base-dark` | To be used on light backgrounds | | `--primary` | To use the primary back office color | | `--secondary` | To use the secondary back office color | ``` ``` ### Icon size variants The default icon size in the back office is `32px`. To change the default size, in the template add the modifier to the class name. ``` ``` The list of available icon sizes: | Modifier | Size | | ---------------- | ------ | | `--tiny` | `8px` | | `--tiny-small` | `12px` | | `--small` | `16px` | | `--small-medium` | `20px` | | `--medium` | `24px` | | `--medium-large` | `38px` | | `--large` | `48px` | | `--extra-large` | `64px` | ### Icons reference The following icons are available out-of-the-box: | Icon | Identifier | | --------------------------------------- | ------------------------------ | | *[Image: accessibility]* | `accessibility` | | *[Image: action-compare]* | `action-compare` | | *[Image: action-compare-versions]* | `action-compare-versions` | | *[Image: action-redo]* | `action-redo` | | *[Image: action-undo]* | `action-undo` | | *[Image: activate]* | `activate` | | *[Image: activity-clock]* | `activity-clock` | | *[Image: add]* | `add` | | *[Image: add-circle]* | `add-circle` | | *[Image: ai]* | `ai` | | *[Image: alert-error]* | `alert-error` | | *[Image: alert-warning]* | `alert-warning` | | *[Image: align-block-center]* | `align-block-center` | | *[Image: align-block-left]* | `align-block-left` | | *[Image: align-block-right]* | `align-block-right` | | *[Image: align-text-center]* | `align-text-center` | | *[Image: align-text-justified]* | `align-text-justified` | | *[Image: align-text-left]* | `align-text-left` | | *[Image: align-text-right]* | `align-text-right` | | *[Image: app]* | `app` | | *[Image: app-blog]* | `app-blog` | | *[Image: app-drawer]* | `app-drawer` | | *[Image: app-edit]* | `app-edit` | | *[Image: app-money]* | `app-money` | | *[Image: app-preview]* | `app-preview` | | *[Image: app-recent]* | `app-recent` | | *[Image: app-settings]* | `app-settings` | | *[Image: app-user]* | `app-user` | | *[Image: app-www]* | `app-www` | | *[Image: archived-restore]* | `archived-restore` | | *[Image: archived-version]* | `archived-version` | | *[Image: arrow-caret-down]* | `arrow-caret-down` | | *[Image: arrow-caret-left]* | `arrow-caret-left` | | *[Image: arrow-caret-right]* | `arrow-caret-right` | | *[Image: arrow-caret-up]* | `arrow-caret-up` | | *[Image: arrow-chevron-down]* | `arrow-chevron-down` | | *[Image: arrow-chevron-left]* | `arrow-chevron-left` | | *[Image: arrow-chevron-right]* | `arrow-chevron-right` | | *[Image: arrow-chevron-up]* | `arrow-chevron-up` | | *[Image: arrow-collapse-expand]* | `arrow-collapse-expand` | | *[Image: arrow-collapse-left]* | `arrow-collapse-left` | | *[Image: arrow-collapse-right]* | `arrow-collapse-right` | | *[Image: arrow-decrease]* | `arrow-decrease` | | *[Image: arrow-double-left]* | `arrow-double-left` | | *[Image: arrow-double-right]* | `arrow-double-right` | | *[Image: arrow-down]* | `arrow-down` | | *[Image: arrow-down-text]* | `arrow-down-text` | | *[Image: arrow-expand-left]* | `arrow-expand-left` | | *[Image: arrow-expand-right]* | `arrow-expand-right` | | *[Image: arrow-increase]* | `arrow-increase` | | *[Image: arrow-left]* | `arrow-left` | | *[Image: arrow-move-left]* | `arrow-move-left` | | *[Image: arrow-move-right]* | `arrow-move-right` | | *[Image: arrow-reload-dot]* | `arrow-reload-dot` | | *[Image: arrow-restore]* | `arrow-restore` | | *[Image: arrow-right]* | `arrow-right` | | *[Image: arrow-rotate]* | `arrow-rotate` | | *[Image: arrow-to-down]* | `arrow-to-down` | | *[Image: arrow-to-down-circle]* | `arrow-to-down-circle` | | *[Image: arrow-to-left]* | `arrow-to-left` | | *[Image: arrow-to-right]* | `arrow-to-right` | | *[Image: arrow-to-up]* | `arrow-to-up` | | *[Image: arrow-up]* | `arrow-up` | | *[Image: arrow-up-text]* | `arrow-up-text` | | *[Image: arrow-upgrade]* | `arrow-upgrade` | | *[Image: arrows-chevron-up-and-down]* | `arrows-chevron-up-and-down` | | *[Image: arrows-circle]* | `arrows-circle` | | *[Image: arrows-full-view]* | `arrows-full-view` | | *[Image: arrows-full-view-out]* | `arrows-full-view-out` | | *[Image: arrows-inside]* | `arrows-inside` | | *[Image: arrows-outside]* | `arrows-outside` | | *[Image: arrows-recycle]* | `arrows-recycle` | | *[Image: arrows-reload]* | `arrows-reload` | | *[Image: arrows-reload-user]* | `arrows-reload-user` | | *[Image: arrows-right-and-left]* | `arrows-right-and-left` | | *[Image: arrows-round]* | `arrows-round` | | *[Image: arrows-switch]* | `arrows-switch` | | *[Image: arrows-synchronize]* | `arrows-synchronize` | | *[Image: arrows-up-and-down]* | `arrows-up-and-down` | | *[Image: assign]* | `assign` | | *[Image: assign-tag]* | `assign-tag` | | *[Image: assign-user]* | `assign-user` | | *[Image: automation]* | `automation` | | *[Image: badge-certificate-horizontal]* | `badge-certificate-horizontal` | | *[Image: badge-certificate-vertical]* | `badge-certificate-vertical` | | *[Image: badge-star]* | `badge-star` | | *[Image: banner]* | `banner` | | *[Image: bell]* | `bell` | | *[Image: block-add]* | `block-add` | | *[Image: block-hidden]* | `block-hidden` | | *[Image: block-lock]* | `block-lock` | | *[Image: block-visible]* | `block-visible` | | *[Image: book]* | `book` | | *[Image: book-open]* | `book-open` | | *[Image: bookmark-filled]* | `bookmark-filled` | | *[Image: bookmark-outline]* | `bookmark-outline` | | *[Image: bookmarks]* | `bookmarks` | | *[Image: box-component]* | `box-component` | | *[Image: bulb-idea]* | `bulb-idea` | | *[Image: business-card]* | `business-card` | | *[Image: calculator]* | `calculator` | | *[Image: calendar]* | `calendar` | | *[Image: calendar-add]* | `calendar-add` | | *[Image: calendar-back]* | `calendar-back` | | *[Image: calendar-block]* | `calendar-block` | | *[Image: calendar-check]* | `calendar-check` | | *[Image: calendar-clock]* | `calendar-clock` | | *[Image: calendar-discard]* | `calendar-discard` | | *[Image: calendar-hidden]* | `calendar-hidden` | | *[Image: calendar-number]* | `calendar-number` | | *[Image: calendar-reload]* | `calendar-reload` | | *[Image: calendar-schedule]* | `calendar-schedule` | | *[Image: calendar-visible]* | `calendar-visible` | | *[Image: camera]* | `camera` | | *[Image: car]* | `car` | | *[Image: car-truck]* | `car-truck` | | *[Image: catalog]* | `catalog` | | *[Image: chart-area]* | `chart-area` | | *[Image: chart-area-line]* | `chart-area-line` | | *[Image: chart-bar]* | `chart-bar` | | *[Image: chart-donut]* | `chart-donut` | | *[Image: chart-donut-element]* | `chart-donut-element` | | *[Image: chart-dots]* | `chart-dots` | | *[Image: chart-dots-other]* | `chart-dots-other` | | *[Image: chart-gauges]* | `chart-gauges` | | *[Image: chart-histogram]* | `chart-histogram` | | *[Image: chart-line]* | `chart-line` | | *[Image: chart-line-graph]* | `chart-line-graph` | | *[Image: check-circle]* | `check-circle` | | *[Image: chevron-down-circle]* | `chevron-down-circle` | | *[Image: chevron-left-circle]* | `chevron-left-circle` | | *[Image: chevron-right-circle]* | `chevron-right-circle` | | *[Image: chevron-up-circle]* | `chevron-up-circle` | | *[Image: clipboard-check]* | `clipboard-check` | | *[Image: clipboard-list]* | `clipboard-list` | | *[Image: clock]* | `clock` | | *[Image: clock-play]* | `clock-play` | | *[Image: cloud]* | `cloud` | | *[Image: cloud-carbon]* | `cloud-carbon` | | *[Image: cloud-check]* | `cloud-check` | | *[Image: cloud-discard]* | `cloud-discard` | | *[Image: cloud-download]* | `cloud-download` | | *[Image: cloud-error]* | `cloud-error` | | *[Image: cloud-synch]* | `cloud-synch` | | *[Image: collaboration]* | `collaboration` | | *[Image: collection]* | `collection` | | *[Image: collection-products]* | `collection-products` | | *[Image: column-one]* | `column-one` | | *[Image: column-two]* | `column-two` | | *[Image: company]* | `company` | | *[Image: connection]* | `connection` | | *[Image: connection-erp]* | `connection-erp` | | *[Image: content-tree]* | `content-tree` | | *[Image: content-tree-arrow-up]* | `content-tree-arrow-up` | | *[Image: content-tree-copy]* | `content-tree-copy` | | *[Image: content-tree-create-location]* | `content-tree-create-location` | | *[Image: content-tree-restore-parent]* | `content-tree-restore-parent` | | *[Image: content-tree-site-structure]* | `content-tree-site-structure` | | *[Image: copy]* | `copy` | | *[Image: copyright]* | `copyright` | | *[Image: core]* | `core` | | *[Image: credit-card]* | `credit-card` | | *[Image: credit-card-hourglass]* | `credit-card-hourglass` | | *[Image: credit-card-payment]* | `credit-card-payment` | | *[Image: crop]* | `crop` | | *[Image: cursor]* | `cursor` | | *[Image: cursor-clicked]* | `cursor-clicked` | | *[Image: cursor-clicked-hand]* | `cursor-clicked-hand` | | *[Image: cursor-hand]* | `cursor-hand` | | *[Image: cursor-hand-click]* | `cursor-hand-click` | | *[Image: cursor-hand-grab]* | `cursor-hand-grab` | | *[Image: cursor-hand-pointer]* | `cursor-hand-pointer` | | *[Image: cursor-hand-swipe]* | `cursor-hand-swipe` | | *[Image: dashboard]* | `dashboard` | | *[Image: dashboard-type]* | `dashboard-type` | | *[Image: database]* | `database` | | *[Image: database-settings]* | `database-settings` | | *[Image: database-share]* | `database-share` | | *[Image: database-synch]* | `database-synch` | | *[Image: deactivate]* | `deactivate` | | *[Image: device-desktop-all-in-one]* | `device-desktop-all-in-one` | | *[Image: device-laptop]* | `device-laptop` | | *[Image: device-mobile]* | `device-mobile` | | *[Image: device-monitor]* | `device-monitor` | | *[Image: device-monitor-card]* | `device-monitor-card` | | *[Image: device-monitor-check]* | `device-monitor-check` | | *[Image: device-monitor-package]* | `device-monitor-package` | | *[Image: device-monitor-settings]* | `device-monitor-settings` | | *[Image: device-monitor-type]* | `device-monitor-type` | | *[Image: device-monitor-user]* | `device-monitor-user` | | *[Image: device-tablet]* | `device-tablet` | | *[Image: discard]* | `discard` | | *[Image: discard-circle]* | `discard-circle` | | *[Image: discount]* | `discount` | | *[Image: discount-ticket]* | `discount-ticket` | | *[Image: download]* | `download` | | *[Image: draft]* | `draft` | | *[Image: drag]* | `drag` | | *[Image: drag-and-drop]* | `drag-and-drop` | | *[Image: duplicate]* | `duplicate` | | *[Image: edit]* | `edit` | | *[Image: edit-draft]* | `edit-draft` | | *[Image: edit-draft-clock]* | `edit-draft-clock` | | *[Image: exclamation-mark]* | `exclamation-mark` | | *[Image: facebook]* | `facebook` | | *[Image: factbox]* | `factbox` | | *[Image: favourite-filled]* | `favourite-filled` | | *[Image: favourite-outline]* | `favourite-outline` | | *[Image: feather]* | `feather` | | *[Image: file]* | `file` | | *[Image: file-add]* | `file-add` | | *[Image: file-arrow-up]* | `file-arrow-up` | | *[Image: file-badge-certificate]* | `file-badge-certificate` | | *[Image: file-code]* | `file-code` | | *[Image: file-copyright]* | `file-copyright` | | *[Image: file-css]* | `file-css` | | *[Image: file-edit]* | `file-edit` | | *[Image: file-history]* | `file-history` | | *[Image: file-info]* | `file-info` | | *[Image: file-js]* | `file-js` | | *[Image: file-link]* | `file-link` | | *[Image: file-pdf]* | `file-pdf` | | *[Image: file-php]* | `file-php` | | *[Image: file-settings]* | `file-settings` | | *[Image: file-statistics]* | `file-statistics` | | *[Image: file-text]* | `file-text` | | *[Image: file-text-edit]* | `file-text-edit` | | *[Image: file-text-money]* | `file-text-money` | | *[Image: file-text-other]* | `file-text-other` | | *[Image: file-text-question-mark]* | `file-text-question-mark` | | *[Image: file-text-search]* | `file-text-search` | | *[Image: file-text-write]* | `file-text-write` | | *[Image: file-type]* | `file-type` | | *[Image: file-warning]* | `file-warning` | | *[Image: filters]* | `filters` | | *[Image: filters-funnel]* | `filters-funnel` | | *[Image: flag]* | `flag` | | *[Image: flip-horizontal]* | `flip-horizontal` | | *[Image: flip-vertical]* | `flip-vertical` | | *[Image: focus-centered]* | `focus-centered` | | *[Image: focus-target]* | `focus-target` | | *[Image: folder]* | `folder` | | *[Image: folder-browse]* | `folder-browse` | | *[Image: folder-open]* | `folder-open` | | *[Image: folder-open-move]* | `folder-open-move` | | *[Image: folders]* | `folders` | | *[Image: forbidden]* | `forbidden` | | *[Image: form-captcha]* | `form-captcha` | | *[Image: form-check]* | `form-check` | | *[Image: form-check-list]* | `form-check-list` | | *[Image: form-check-square]* | `form-check-square` | | *[Image: form-checkbox]* | `form-checkbox` | | *[Image: form-data]* | `form-data` | | *[Image: form-dropdown]* | `form-dropdown` | | *[Image: form-input]* | `form-input` | | *[Image: form-input-check]* | `form-input-check` | | *[Image: form-input-hidden]* | `form-input-hidden` | | *[Image: form-input-multi-line]* | `form-input-multi-line` | | *[Image: form-input-number]* | `form-input-number` | | *[Image: form-input-rename]* | `form-input-rename` | | *[Image: form-input-single-line]* | `form-input-single-line` | | *[Image: form-input-visible]* | `form-input-visible` | | *[Image: form-radio]* | `form-radio` | | *[Image: form-radio-list]* | `form-radio-list` | | *[Image: handshake]* | `handshake` | | *[Image: hash]* | `hash` | | *[Image: header-1]* | `header-1` | | *[Image: header-2]* | `header-2` | | *[Image: header-3]* | `header-3` | | *[Image: header-4]* | `header-4` | | *[Image: header-5]* | `header-5` | | *[Image: header-6]* | `header-6` | | *[Image: headphones-support]* | `headphones-support` | | *[Image: heart]* | `heart` | | *[Image: help]* | `help` | | *[Image: hierarchy-circle]* | `hierarchy-circle` | | *[Image: hierarchy-circle-more]* | `hierarchy-circle-more` | | *[Image: hierarchy-items]* | `hierarchy-items` | | *[Image: hierarchy-schema]* | `hierarchy-schema` | | *[Image: hierarchy-site-map]* | `hierarchy-site-map` | | *[Image: hierarchy-square]* | `hierarchy-square` | | *[Image: hierarchy-square-more]* | `hierarchy-square-more` | | *[Image: hierarchy-topology]* | `hierarchy-topology` | | *[Image: history]* | `history` | | *[Image: home]* | `home` | | *[Image: home-settings]* | `home-settings` | | *[Image: image]* | `image` | | *[Image: image-edit]* | `image-edit` | | *[Image: image-focus]* | `image-focus` | | *[Image: image-gallery]* | `image-gallery` | | *[Image: image-insert]* | `image-insert` | | *[Image: image-upload]* | `image-upload` | | *[Image: info-circle]* | `info-circle` | | *[Image: info-rounded]* | `info-rounded` | | *[Image: info-square]* | `info-square` | | *[Image: layout]* | `layout` | | *[Image: layout-navbar]* | `layout-navbar` | | *[Image: layout-navbar-add]* | `layout-navbar-add` | | *[Image: layout-navbar-preview]* | `layout-navbar-preview` | | *[Image: layout-navbar-visible]* | `layout-navbar-visible` | | *[Image: layout-switch]* | `layout-switch` | | *[Image: lift]* | `lift` | | *[Image: lightning]* | `lightning` | | *[Image: like]* | `like` | | *[Image: like-shine]* | `like-shine` | | *[Image: line-vertical]* | `line-vertical` | | *[Image: link]* | `link` | | *[Image: link-anchor]* | `link-anchor` | | *[Image: list-bullet]* | `list-bullet` | | *[Image: list-content]* | `list-content` | | *[Image: list-number]* | `list-number` | | *[Image: list-tasks]* | `list-tasks` | | *[Image: lock]* | `lock` | | *[Image: lock-focus]* | `lock-focus` | | *[Image: lock-rounded]* | `lock-rounded` | | *[Image: log-in]* | `log-in` | | *[Image: log-out]* | `log-out` | | *[Image: magnet]* | `magnet` | | *[Image: measure-ruler-bent]* | `measure-ruler-bent` | | *[Image: measure-ruler-straight]* | `measure-ruler-straight` | | *[Image: media-type]* | `media-type` | | *[Image: menu-hamburger]* | `menu-hamburger` | | *[Image: menu-hamburger-aligned]* | `menu-hamburger-aligned` | | *[Image: merge]* | `merge` | | *[Image: message]* | `message` | | *[Image: message-blog-post]* | `message-blog-post` | | *[Image: message-bubble]* | `message-bubble` | | *[Image: message-bubble-dots]* | `message-bubble-dots` | | *[Image: message-bubble-edit]* | `message-bubble-edit` | | *[Image: message-bubble-info]* | `message-bubble-info` | | *[Image: message-bubble-quote]* | `message-bubble-quote` | | *[Image: message-edit]* | `message-edit` | | *[Image: message-email]* | `message-email` | | *[Image: message-email-read]* | `message-email-read` | | *[Image: message-empty]* | `message-empty` | | *[Image: message-exchange]* | `message-exchange` | | *[Image: message-text]* | `message-text` | | *[Image: microphone]* | `microphone` | | *[Image: minus]* | `minus` | | *[Image: minus-circle]* | `minus-circle` | | *[Image: money-bag]* | `money-bag` | | *[Image: money-bills]* | `money-bills` | | *[Image: money-coin]* | `money-coin` | | *[Image: money-coins]* | `money-coins` | | *[Image: mood-happy-face]* | `mood-happy-face` | | *[Image: mood-sad-face]* | `mood-sad-face` | | *[Image: more]* | `more` | | *[Image: mountain]* | `mountain` | | *[Image: news]* | `news` | | *[Image: note]* | `note` | | *[Image: note-blog]* | `note-blog` | | *[Image: note-check]* | `note-check` | | *[Image: note-text]* | `note-text` | | *[Image: notebook]* | `notebook` | | *[Image: notebook-text]* | `notebook-text` | | *[Image: notes-list]* | `notes-list` | | *[Image: official-building]* | `official-building` | | *[Image: open-new-window]* | `open-new-window` | | *[Image: open-same-window]* | `open-same-window` | | *[Image: overdue]* | `overdue` | | *[Image: path-route]* | `path-route` | | *[Image: path-two-directions]* | `path-two-directions` | | *[Image: pause]* | `pause` | | *[Image: pen-write]* | `pen-write` | | *[Image: phone]* | `phone` | | *[Image: pin]* | `pin` | | *[Image: pin-location]* | `pin-location` | | *[Image: pin-location-money]* | `pin-location-money` | | *[Image: pin-location-question-mark]* | `pin-location-question-mark` | | *[Image: pins-locations]* | `pins-locations` | | *[Image: plane]* | `plane` | | *[Image: price]* | `price` | | *[Image: product]* | `product` | | *[Image: product-arrow-down]* | `product-arrow-down` | | *[Image: product-catalog]* | `product-catalog` | | *[Image: product-catalog-number]* | `product-catalog-number` | | *[Image: product-check]* | `product-check` | | *[Image: product-clock]* | `product-clock` | | *[Image: product-collection]* | `product-collection` | | *[Image: product-discard]* | `product-discard` | | *[Image: product-search]* | `product-search` | | *[Image: product-settings]* | `product-settings` | | *[Image: product-tag]* | `product-tag` | | *[Image: product-variant]* | `product-variant` | | *[Image: prompt]* | `prompt` | | *[Image: qa-admin]* | `qa-admin` | | *[Image: qa-catalog]* | `qa-catalog` | | *[Image: qa-click]* | `qa-click` | | *[Image: qa-clipboard]* | `qa-clipboard` | | *[Image: qa-cloud]* | `qa-cloud` | | *[Image: qa-company]* | `qa-company` | | *[Image: qa-editor]* | `qa-editor` | | *[Image: qa-file]* | `qa-file` | | *[Image: qa-form-check]* | `qa-form-check` | | *[Image: qa-info]* | `qa-info` | | *[Image: qa-product]* | `qa-product` | | *[Image: qa-store]* | `qa-store` | | *[Image: quote]* | `quote` | | *[Image: receipt]* | `receipt` | | *[Image: receipt-check]* | `receipt-check` | | *[Image: receipt-clock]* | `receipt-clock` | | *[Image: receipt-number]* | `receipt-number` | | *[Image: receipt-settings]* | `receipt-settings` | | *[Image: reveal]* | `reveal` | | *[Image: robot]* | `robot` | | *[Image: rocket]* | `rocket` | | *[Image: sales-revenue]* | `sales-revenue` | | *[Image: save]* | `save` | | *[Image: save-exit]* | `save-exit` | | *[Image: search]* | `search` | | *[Image: seen-this]* | `seen-this` | | *[Image: segments]* | `segments` | | *[Image: send]* | `send` | | *[Image: send-review]* | `send-review` | | *[Image: server]* | `server` | | *[Image: settings]* | `settings` | | *[Image: settings-cog]* | `settings-cog` | | *[Image: settings-configure]* | `settings-configure` | | *[Image: share]* | `share` | | *[Image: shield]* | `shield` | | *[Image: shipment]* | `shipment` | | *[Image: shipment-arrow]* | `shipment-arrow` | | *[Image: shipment-free]* | `shipment-free` | | *[Image: shop]* | `shop` | | *[Image: shopping-basket]* | `shopping-basket` | | *[Image: shopping-cart]* | `shopping-cart` | | *[Image: shopping-cart-add]* | `shopping-cart-add` | | *[Image: shopping-cart-arrow-up]* | `shopping-cart-arrow-up` | | *[Image: shopping-cart-heart]* | `shopping-cart-heart` | | *[Image: shopping-cart-settings]* | `shopping-cart-settings` | | *[Image: shopping-cart-star]* | `shopping-cart-star` | | *[Image: signal-radio]* | `signal-radio` | | *[Image: signal-rss]* | `signal-rss` | | *[Image: signal-wifi]* | `signal-wifi` | | *[Image: site]* | `site` | | *[Image: sites]* | `sites` | | *[Image: slider]* | `slider` | | *[Image: speaker]* | `speaker` | | *[Image: square]* | `square` | | *[Image: square-selection]* | `square-selection` | | *[Image: stack-overflow]* | `stack-overflow` | | *[Image: star-badge]* | `star-badge` | | *[Image: star-circle]* | `star-circle` | | *[Image: stars]* | `stars` | | *[Image: suitcase]* | `suitcase` | | *[Image: table-add]* | `table-add` | | *[Image: table-cell]* | `table-cell` | | *[Image: table-column]* | `table-column` | | *[Image: table-row]* | `table-row` | | *[Image: table-settings-column]* | `table-settings-column` | | *[Image: tag]* | `tag` | | *[Image: tag-settings]* | `tag-settings` | | *[Image: tags]* | `tags` | | *[Image: target]* | `target` | | *[Image: target-click]* | `target-click` | | *[Image: target-dynamic]* | `target-dynamic` | | *[Image: target-location]* | `target-location` | | *[Image: target-other]* | `target-other` | | *[Image: telephone]* | `telephone` | | *[Image: text-bold]* | `text-bold` | | *[Image: text-code]* | `text-code` | | *[Image: text-embedded]* | `text-embedded` | | *[Image: text-embedded-inline]* | `text-embedded-inline` | | *[Image: text-italic]* | `text-italic` | | *[Image: text-paragraph]* | `text-paragraph` | | *[Image: text-paragraph-add]* | `text-paragraph-add` | | *[Image: text-slash]* | `text-slash` | | *[Image: text-strikethrough]* | `text-strikethrough` | | *[Image: text-subscript]* | `text-subscript` | | *[Image: text-superscript]* | `text-superscript` | | *[Image: text-underline]* | `text-underline` | | *[Image: timeline]* | `timeline` | | *[Image: tool]* | `tool` | | *[Image: tool-group]* | `tool-group` | | *[Image: tools]* | `tools` | | *[Image: translation-language]* | `translation-language` | | *[Image: trash]* | `trash` | | *[Image: trash-discard]* | `trash-discard` | | *[Image: trash-open]* | `trash-open` | | *[Image: trash-send]* | `trash-send` | | *[Image: twitter]* | `twitter` | | *[Image: umbrella]* | `umbrella` | | *[Image: unarchive]* | `unarchive` | | *[Image: unassign-tag]* | `unassign-tag` | | *[Image: unlink]* | `unlink` | | *[Image: unlock]* | `unlock` | | *[Image: unpin]* | `unpin` | | *[Image: upload]* | `upload` | | *[Image: user]* | `user` | | *[Image: user-add]* | `user-add` | | *[Image: user-admin]* | `user-admin` | | *[Image: user-block]* | `user-block` | | *[Image: user-cart]* | `user-cart` | | *[Image: user-check]* | `user-check` | | *[Image: user-customer]* | `user-customer` | | *[Image: user-customer-number]* | `user-customer-number` | | *[Image: user-edit]* | `user-edit` | | *[Image: user-editor]* | `user-editor` | | *[Image: user-focus]* | `user-focus` | | *[Image: user-group]* | `user-group` | | *[Image: user-group-customer]* | `user-group-customer` | | *[Image: user-id]* | `user-id` | | *[Image: user-mail]* | `user-mail` | | *[Image: user-money]* | `user-money` | | *[Image: user-profile]* | `user-profile` | | *[Image: user-target]* | `user-target` | | *[Image: user-type]* | `user-type` | | *[Image: users-add]* | `users-add` | | *[Image: variation-1-1]* | `variation-1-1` | | *[Image: variation-16-9]* | `variation-16-9` | | *[Image: variation-3-2]* | `variation-3-2` | | *[Image: variation-4-3]* | `variation-4-3` | | *[Image: variation-custom]* | `variation-custom` | | *[Image: video]* | `video` | | *[Image: video-play]* | `video-play` | | *[Image: view-custom]* | `view-custom` | | *[Image: view-grid]* | `view-grid` | | *[Image: view-list]* | `view-list` | | *[Image: view-panels]* | `view-panels` | | *[Image: vinyl]* | `vinyl` | | *[Image: visibility]* | `visibility` | | *[Image: visibility-hidden]* | `visibility-hidden` | | *[Image: wand]* | `wand` | | *[Image: workflow]* | `workflow` | | *[Image: world]* | `world` | | *[Image: world-add]* | `world-add` | | *[Image: world-cursor]* | `world-cursor` | | *[Image: world-settings]* | `world-settings` | | *[Image: x]* | `x` | | *[Image: zoom-in]* | `zoom-in` | | *[Image: zoom-out]* | `zoom-out` | ## `ibexa_content_type_icon()` `ibexa_content_type_icon()` generates a path to a content type icon. | Argument | Type | Description | | -------------- | -------- | ------------------------ | | `content_type` | `string` | Content type identifier. | See [Customize content type icons](https://doc.ibexa.co/en/latest/administration/back_office/back_office_elements/custom_icons/#customize-content-type-icons) to associate icons to content types. # Image Twig functions - [`ibexa_image_alias`](#ibexa_image_alias) returns the selected variation of an image field. - [`ibexa_content_field_identifier_first_filled_image`](#ibexa_content_field_identifier_first_filled_image) returns the identifier of the first image field in a content item that isn't empty. ## Image rendering To render images, use the [`ibexa_render_field()`](https://doc.ibexa.co/en/latest/templating/twig_function_reference/field_twig_functions/#ibexa_render_field) Twig function with the variation name passed as an argument, for example: ``` {{ ibexa_render_field(content, 'image', { 'template': '@ibexadesign/fields/image.html.twig', 'attr': {class: 'thumbnail-image'}, 'parameters': { 'alias': 'small' } }) }} ``` ## Image information ### `ibexa_image_alias()` `ibexa_image_alias()` returns the selected variation of an image field. | Argument | Type | Description | | ------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------- | | `field` | `Ibexa\Contracts\Core\Repository\Values\Content\Field` | The image field. | | `versionInfo` | `Ibexa\Contracts\Core\Repository\Values\Content\VersionInfo` | The VersionInfo that the field belongs to. | | `variantName` | `string` | Name of the image variation to be used. To display the original image variation, use `original` as the variation name. | ``` {% set thumbnail = ibexa_image_alias(imageField, content.versionInfo, 'small') %} ``` > **Tip: Tip** > > You can access the name of a variation from the variation object with `variation.name`. You can, for example, use it as parameter in the [`ibexa_render_field()`](https://doc.ibexa.co/en/latest/templating/twig_function_reference/field_twig_functions/#ibexa_render_field) Twig function. ### `ibexa_content_field_identifier_first_filled_image()` `ibexa_content_field_identifier_first_filled_image()` returns the identifier of the first image field that isn't empty. > **Caution: Caution** > > This function works only for [Image](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/imagefield/index.md) fields. It doesn't work for [ImageAsset](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/imageassetfield/index.md) fields. | Argument | Type | Description | | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | | `content` | [`Content`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Content.html) or [`ContentAwareInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-ContentAwareInterface.html) | Content item to display the image for. | ``` {% set firstImage = ibexa_content_field_identifier_first_filled_image(content) %} ``` ``` {% set firstImage = ibexa_content_field_identifier_first_filled_image(product) %} ``` #### Examples You can use `ibexa_content_field_identifier_first_filled_image()` to find and render the first existing image in an article: ``` {% set firstImage = ibexa_content_field_identifier_first_filled_image(content) %} {{ ibexa_render_field(content, firstImage) }} ``` # Page Twig functions Editions: Experience ## `ibexa_append_cacheable_query_params()` Get the query parameters of a page block as [configured in `cacheable_query_params`](https://doc.ibexa.co/en/latest/content_management/pages/page_blocks/#block-configuration). If the block type has no configured query parameters, an empty array is returned. ``` {{ render_esi(controller('Ibexa\\Bundle\\FieldTypePage\\Controller\\BlockController::renderAction', { 'locationId': locationId, 'blockId': block.id, 'versionNo': versionInfo.versionNo, 'languageCode': field.languageCode }, ibexa_append_cacheable_query_params(block))) }} ``` ## `ibexa_page_layout()` Get the layout template of a landing page. ``` {% include ibexa_page_layout(page) with {'zones': page.zones} %} ``` It can be used to render a [page field](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/pagefield/index.md). For an example, you can look at how the default `vendor/ibexa/fieldtype-page/src/bundle/Resources/views/fields/ibexa_landing_page.html.twig` uses it. # Product Twig functions ### `ibexa_get_product` The `ibexa_get_product()` filter gets the selected product based on either a product object or a content item object that contains a product. #### Examples ``` {{ (product|ibexa_get_product).code }} {{ (content|ibexa_get_product).code }} ``` ### `ibexa_format_product_attribute` The `ibexa_format_product_attribute` filter formats the attribute value to a readable, translated form. Rendering is performed by using Twig templates with named blocks, defined in a configurable list. You can customize this behavior by adding templates or by listening to the [`ProductAttributeRenderEvent`](https://doc.ibexa.co/en/latest/api/event_reference/product_catalog_events/#attribute-rendering). For more information, see [Customize product attribute templates](https://doc.ibexa.co/en/latest/product_catalog/customize_product_attribute_templates/index.md). #### Examples ``` {% for attribute in product.attributes %} {{ attribute|ibexa_format_product_attribute }} {% endfor %} ``` ### `ibexa_product` `ibexa_product` enables you to check whether the provided object is a product. #### Examples ``` {$ if content is ibexa_product %} ``` ### `ibexa_has_product_availability` The `ibexa_has_product_availability` Twig function is used to check whether a product has defined availability. #### Examples ``` {% if ibexa_has_product_availability(product) %} {% else %} {% endif %} ``` ### `ibexa_get_product_availability` The `ibexa_get_product_availability` Twig function retrieves the availability for a product. #### Examples ``` {% set availability = ibexa_get_product_availability(product) %} {% if availability %} Availability: {{ availability }} {% else %} Availability: Out of stock. {% endif %} ``` ### `ibexa_is_product_available` The `ibexa_is_product_available` Twig function checks whether a product is available for purchase based on its availability status. #### Examples ``` {% if ibexa_is_product_available(product) %} Add to cart {% else %} Out of stock {% endif %} ``` ### `ibexa_get_product_stock` The `ibexa_get_product_stock` Twig function retrieves the stock quantity for a product. #### Examples ``` {% set stock = ibexa_get_product_stock(product) %} {% if stock > 0 %} In stock: {{ stock }} items {% else %} Out of stock {% endif %} ``` ### `ibexa_format_price` The `ibexa_format_price` filter formats the price value by placing currency code either on the left or on the right of the numerical value. #### Examples ``` {% for product.price in product.attributes %} {{ product.price.getMoney()|ibexa_format_price }} {% endfor %} ``` ### `ibexa_is_pim_local` The `ibexa_is_pim_local` is a helper Twig function that enables changing the behavior of templates depending on the source of product data. #### Examples ``` {% if ibexa_is_pim_local() == true %}
    {% endif %} ``` ### `ibexa_product_catalog_group_attributes` The `ibexa_product_catalog_group_attributes` filter groups product attributes based on the [attribute group](https://doc.ibexa.co/projects/userguide/en/5.0/pim/work_with_product_attributes/#create-attribute-groups) they belong to. #### Example ``` {% for group, attributes in product.attributes | ibexa_product_catalog_group_attributes %}
      {{ group.name | capitalize }} {% for attribute in attributes %} {% set attribute_definition = attribute.attributeDefinition %}
    • {{ attribute_definition.name }} : {{ attribute | ibexa_format_product_attribute }}
    • {% endfor %}
    {% endfor %} ``` # Quable Twig functions The [Quable connector](https://doc.ibexa.co/en/latest/product_catalog/quable/quable/index.md) provides the following Twig functions: ## `ibexa_quable_instance_url()` Returns the [configured Quable instance URL](https://doc.ibexa.co/en/latest/product_catalog/quable/configure_quable_connector/#configuration-example), value of the `ibexa_connector_quable.instance_url` parameter. You can use it to inject a link to the Quable's back office into Ibexa DXP's back office, to improve the experience for your editors. ### Example ``` Manage in Quable ``` # Recommendations Twig functions The following Twig functions are supported while using [Raptor connector](https://doc.ibexa.co/en/latest/recommendations/raptor_integration/raptor_connector/index.md): ## `ibexa_tracking_script()` function The `ibexa_tracking_script()` Twig function allows you to embed the main tracking script into the website. It loads the initial script into `window.raptor`. The script then enables event tracking, such as page visits, product views, or buys, from the front end. It can be overridden in multiple ways to support custom implementations and to render code snippet through Ibexa DXP in the [design engine](https://doc.ibexa.co/en/latest/templating/design_engine/design_engine/index.md). Tracking can be conditionally initialized depending on cookie consent logic. By default, for client-side use, the function returns a script, but it can return nothing when used server-side. This function accepts the following parameters: | Parameter | Type | Default value | Remarks | | -------------- | ------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `customerId` | string | From SiteAccess configuration | [Raptor account ID](https://doc.ibexa.co/en/latest/recommendations/raptor_integration/connector_installation_configuration/#customer-id). Can be overridden for custom customer IDs. | | `hasConsented` | boolean | false | Controls loading of tracking based on user consent at render time. | Default setup: ``` {{ ibexa_tracking_script() }} ``` Example setup using parameters: ``` {{ ibexa_tracking_script(customerId: '123', hasConsented: true) }} ``` If the custom `customerId` parameter is not set, the function uses the `customerID` from the [connector configuration](https://doc.ibexa.co/en/latest/recommendations/raptor_integration/connector_installation_configuration/#siteaccess-aware-configuration) to render the tracking script. It can be overridden by providing a custom value if needed. If the `hasConsented` parameter is set to `true` in the template, the tracking script is initialized automatically. This value should be set if user consent for tracking cookies is already known at render time. If `hasConsented` is set to `false`, tracking should be enabled by dispatching a custom JavaScript event after consent is granted, for example through a custom script in layout. If it's set dynamically, avoid enabling the [HTTP cache](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/http_cache/context_aware_cache/index.md) for users without consent. The recommended method to integrate the tracking script with custom front-end logic is to dispatch the `enableTracking` JavaScript event after tracking cookie consent is granted: ``` document.dispatchEvent(new CustomEvent('enableTracking')); ``` > **Note: Note** > > In [Symfony's debug mode](https://symfony.com/doc/7.4/reference/configuration/kernel.html#kernel-debug), the provided script outputs diagnostic information to the console. This output is not included in production environment. ## `ibexa_tracking_track_event()` function The `ibexa_tracking_track_event()` function is responsible for sending event data to the service, which enables tracking of user interactions and behaviors. Tracking is handled through a Twig function that accept following parameters: ``` ibexa_tracking_track_event( eventType, {# string: 'visit', 'contentvisit', 'buy', 'basket', 'itemclick' #} data, {# mixed: product, content, or null (optional) #} context, {# array: additional context data (optional) #} template {# string: custom template path (optional) #} ) ``` - **eventType** - type: string, defines the type of tracking event to be sent, for example, `visit`, `contentvisit`, `buy`, `basket`, `itemclick`. For more information, see [Tracking events for recommendations](https://content.raptorservices.com/help-center/tracking-events-for-recommendation). - **data** (optional) - type: mixed, accepts the primary object associated with the event, such as a Product or Content, can be null if not required. For more information, see [tracking event examples](#tracking-events). - **context** (optional)- type: array, additional event data, such as quantity, basket details, or custom parameters. For more information, see [example usage](#context-parameter-example-usage). - **template** (optional) - type: string, path to a custom Twig template used to render the tracking event, allows overriding the default tracking output. #### Tracking events The following events are supported and can be triggered from Twig templates: ### Product `visit` event This event tracks product page visits by users. It's the most common e-commerce tracking event used to capture product views for analytics, recommendation models, and user behavior processing. Required data: - **Product object** - defines the product being tracked. It implements [`Ibexa\Contracts\ProductCatalog\Values\ProductInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-ProductInterface.html) so the system can read its information (for example, code, price, category). Example: ``` # templates/product/view.html.twig #} {% extends 'base.html.twig' %} {% block content %}

    {{ product.name }}

    {{ product.description }}

    {{ product.price }}
    {# Track product visit #} {{ ibexa_tracking_track_event('visit', product) }} {% endblock %} ``` ### `contentvisit` event This event tracks content page visits by users. It implements [`Ibexa\Contracts\Core\Repository\Values\Content\Content`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Content.html) and can be used to check content views for analytics, personalization, and user behavior tracking. - **Content object** - defines the content being tracked. Example: ``` {# templates/bundles/IbexaCoreBundle/default/content/full.html.twig #} {% extends '@!IbexaCore/default/content/full.html.twig' %} {% block content %} {{ parent() }} {{ ibexa_tracking_track_event('contentvisit', content) }} {% endblock %} ``` ### Product `buy` event This event tracks when a product is bought. - **Product object** defines the product being purchased. - **Context array with purchase conditions** - provides optional data about the product purchase context, like quantity, price, or currency. ``` {% set buyContext = { 'subtotal': '10.00', 'currency': 'EUR', 'quantity': 1 } %} {{ ibexa_tracking_track_event('buy', product, buyContext) }} ``` ### Product `basket` event This event tracks when a product is added to the [cart](https://doc.ibexa.co/en/latest/commerce/cart/cart/index.md). It captures user interactions that indicate interest, which can be used for conversion tracking and to improve product recommendations. Required data: - **Product object** - defines the product being added to the basket. - **Context array with cart information** - provides optional data about the cart, like product quantity or cart identifier, to provide context for the event. Example: ``` {# templates/cart/add_confirmation.html.twig #} {% extends 'base.html.twig' %} {% block content %}

    Product "{{ product.name }}" has been added to your cart!

    Quantity: {{ addedQuantity }}

    {# Build basket content string: "product-code:quantity;product-code:quantity" #} {% set basketContent = [] %} {% for entry in cart.entries %} {% set basketContent = basketContent|merge([entry.product.code ~ ':' ~ entry.quantity]) %} {% endfor %} {# Track basket addition #} {% set basketContext = { 'basketContent': basketContent|join(';'), 'basketId': cart.id, 'quantity': addedQuantity } %} {{ ibexa_tracking_track_event('basket', product, basketContext) }} View Cart {% endblock %} ``` ### `itemclicked` event This event tracks when a user clicks a Raptor recommendation, including adding products to the cart from the recommendation module. Required data: - **Product code** - code of the product the visitor interacted with. - **Context** - provides optional data, like `moduleName` or `redirectUrl`, to provide context for the event. Example: ``` {{ ibexa_tracking_track_event('itemclick', product.code, { 'moduleName': 'homepage-recommendations', 'redirectUrl': path('ibexa.product.view', {'productCode': product.code}) }) }} ``` ### `context` parameter - example usage You can use the `context` parameter to pass additional data. During tracking, for products assigned to multiple categories, the system uses the first category. In this case, `context` allows to override the product category by passing a category identifier: ``` {% block content %}

    {{ product.name }}

    {# ... product content ... #}
    {# Track with category identifier - CategoryID automatic loading and formatting #} {{ ibexa_tracking_track_event('visit', product, { 'categoryIdentifier': 'electronics' }) }} {% endblock %} ``` For other usage examples, see the [`buy`](#product-buy-event) and [`basket`](#product-basket-event) events. ### Custom templates You can create a custom template for tracking in the `/templates/tracking/` directory. See the following example of `custom_visit.html.twig`: ``` {# templates/tracking/custom_visit.html.twig #} {# # Custom visit tracking template # # Available variables, passed to the template by `ibexa_tracking_track_event`: # - parameters: array of Raptor tracking parameters (p1, p2, p3, etc.) # - debug: boolean flag to enable debug console messages #} ``` You can override the default tracking templates by providing a custom template path: ``` {{ ibexa_tracking_track_event( 'visit', product, {}, '@App/tracking/custom_visit.html.twig' ) }} ``` # Site context Twig functions Editions: Experience To determine if given location is site context-aware, you can use site context [Twig test](https://twig.symfony.com/doc/3.x/tests/index.html). ### `ibexa_site_context_aware()` `ibexa_site_context_aware()` checks whether a given location is site context-aware, meaning it's not excluded from Site context by using the `ibexa.system..site_context.excluded_paths` configuration. #### Examples ``` {% if location is ibexa_site_context_aware %}

    I am aware of the site context!

    {% endif %} ``` # Storefront Twig functions Editions: Commerce You can use storefront Twig functions to control the rendering of storefront elements. ### `ibexa_storefront_get_logo()` `ibexa_storefront_get_logo()` returns current shop logo (configured in `ibexa.system..storefront.logo`): ``` {% block logo %} Logo {% endblock %} ``` ### `ibexa_storefront_get_name()` `ibexa_storefront_get_name()` returns current shop name (configured in `ibexa.system..storefront.name`): ``` {% block copyright %} © {{ null|date('Y') }} {{ ibexa_storefront_get_name() }} {% endblock %} ``` ### `ibexa_storefront_get_main_menu_alias()` / `ibexa_storefront_get_main_menu_options()` `ibexa_storefront_get_main_menu_alias()` returns the main menu alias. `ibexa_storefront_get_main_menu_options()` returns the main menu's options (configured in `ibexa.system..storefront.main_menu` / `ibexa.system..storefront.main_menu_options`). ``` {% block main_menu %} {% set main_menu_alias = ibexa_storefront_get_main_menu_alias() %} {% set main_menu_options = ibexa_storefront_get_main_menu_options() %} {{ knp_menu_render(main_menu_alias, main_menu_options) }} {% endblock %} ``` ### `ibexa_storefront_create_inline_product_search_form()` `ibexa_storefront_create_inline_product_search_form()` creates a product search form: ``` {% block search %} {{ form(ibexa_storefront_create_inline_product_search_form()) }} {% endblock %} ``` ### `ibexa_storefront_get_main_category()` `ibexa_storefront_get_main_category()` returns the main (first-level) category for a given category. For example: if a given category is "Desks" and it has the following ancestors: "Furniture" > "Office" > "Desks", then the main category for "Office" is "Furniture". ``` {% set main_category = ibexa_storefront_get_main_category(category) %}

    Main category: {{ main_category.name }}

    ``` ### `ibexa_storefront_get_active_currency()` `ibexa_storefront_get_active_currency()` returns the active currency object (`Ibexa\Contracts\ProductCatalog\Values\CurrencyInterface`). ``` {% set currency = ibexa_storefront_get_active_currency() %}

    Active currency code: {{ currency.code }}

    ``` ### `ibexa_storefront_get_language_name_by_code()` `ibexa_storefront_get_language_name_by_code()` displays language name based on its code or locale. ``` {% set languageName = ibexa_storefront_get_language_name_by_code(languageCode) %}

    Language name: {{ languageName }}

    ``` ### `ibexa_storefront_get_product_render_action()` `ibexa_storefront_get_product_render_action()` returns a rendering action to be used, as defined in [settings](https://doc.ibexa.co/en/latest/commerce/storefront/configure_storefront/index.md). It serves as an alternative for `ibexa_render` which heavily relies on content objects being not present within context of remote PIM. You can use this, for example, to [parametrize the display of products by using a custom controller](https://doc.ibexa.co/en/latest/commerce/storefront/extend_storefront/#generate-custom-product-preview-path). ``` {% if ibexa_is_pim_local() %} {{ ibexa_render(product, { method: 'esi', viewType: 'card' }) }} {% else %} {{ render( controller(ibexa_storefront_get_product_render_action(), { product: product }) ) }} {% endif %} ``` ### `ibexa_get_anonymous_user_id()` `ibexa_get_anonymous_user_id()` returns the configured user ID for the anonymous user (configured in `ibexa.system..anonymous_user_id`). ``` {{ ibexa_get_anonymous_user_id() }} ``` ### `ibexa_storefront_are_discounts_enabled()` This function detects if the [Discounts](https://doc.ibexa.co/en/latest/discounts/discounts_guide/index.md) feature is present. ``` {% if ibexa_storefront_are_discounts_enabled() %}
    {{- product_price_original_subtotal -}}
    {% endif %} ``` # URL Twig functions - [`ibexa_path()`](#ibexa_path) returns the absolute path to a content item or location. - [`ibexa_url()`](#ibexa_url) returns the absolute URL to a content item or location. - [`ibexa.url.alias`](#ibexaurlalias) generates URLs for a location from the given arguments. - [`ibexa_route()`](#ibexa_route) generates a RouteReference object from the given parameters. - [`ibexa_oauth2_connect_path()`](#ibexa_oauth2_connect_path) generates a relative path for the given OAuth2 route. - [`ibexa_oauth2_connect_url()`](#ibexa_oauth2_connect_url) generates an absolute URL for the given OAuth2 route. ## URLs ### `ibexa_path()` `ibexa_path()` returns the absolute path to a content item or location. | Argument | Type | Description | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | | `name` | `string`, `Ibexa\Contracts\Core\Repository\Values\Content\Location`, `Ibexa\Contracts\Core\Repository\Values\Content\Content`, `Ibexa\Contracts\Core\Repository\Values\Content\ContentInfo`, `Ibexa\Contracts\Core\Repository\Values\Content\Location`, `Ibexa\Core\MVC\Symfony\Routing\RouteReference` | The name of the route, location, or content. | | `parameters` | `array` | Route parameters. | | `relative` | `boolean` | Whether to generate a relative path. | ``` {{ ibexa_path(location) }} ``` ### `ibexa_url()` `ibexa_url()` returns the absolute URL to a content item or location. | Argument | Type | Description | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | | `name` | `string`, `Ibexa\Contracts\Core\Repository\Values\Content\Location`, `Ibexa\Contracts\Core\Repository\Values\Content\Content`, `Ibexa\Contracts\Core\Repository\Values\Content\ContentInfo`, `Ibexa\Contracts\Core\Repository\Values\Content\Location`, `Ibexa\Core\MVC\Symfony\Routing\RouteReference` | The name of the route, location, or content. | | `parameters` | `array` | Route parameters. | | `schemeRelative` | `boolean` | Whether to generate a relative URL. | ``` {{ ibexa_url(location, {}, false) }} ``` ### `ibexa.url.alias` `ibexa.url.alias` generates URLs for a Location from the given parameters. > **Note: Note** > > `ibexa.url.alias` is a not a Twig function, but a special route name. For more information about the use of `ibexa.url.alias` as a parameter of the [Symfony `path` Twig function](https://symfony.com/doc/7.4/reference/twig_reference.html#path), see [Links to other locations](https://doc.ibexa.co/en/latest/templating/urls_and_routes/urls_and_routes/index.md). ``` {% set href = path('ibexa.url.alias', { 'locationId': 2 }) %} ``` ### `ibexa_route()` `ibexa_route()` generates a [RouteReference object](https://doc.ibexa.co/en/latest/templating/urls_and_routes/urls_and_routes/#routereference) from the given parameters. | Argument | Type | Description | | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | | `resource` | `string`, `Ibexa\Contracts\Core\Repository\Values\Content\Location`, `Ibexa\Contracts\Core\Repository\Values\Content\Content`, `Ibexa\Contracts\Core\Repository\Values\Content\ContentInfo`, `Ibexa\Contracts\Core\Repository\Values\Content\Location`, `Ibexa\Core\MVC\Symfony\Routing\RouteReference` | Resource or route name. | | `params` | `array` | Route parameters. | ``` {% set routeReference = ibexa_route("ibexa.url.alias", { 'locationId': 2 }) %} ``` ## OAuth2 ### `ibexa_oauth2_connect_path()` `ibexa_oauth2_connect_path()` generates a relative path for the given [OAuth2 client](https://doc.ibexa.co/en/latest/users/oauth_client/index.md). | Argument | Type | Description | | ------------ | --------- | ------------------------------------ | | `identifier` | string | Identifier of the OAuth connection. | | `parameters` | `array` | Route parameters. | | `relative` | `boolean` | Whether to generate a relative path. | ### `ibexa_oauth2_connect_url()` `ibexa_oauth2_connect_url()` generates an absolute URL for the given [OAuth2 client](https://doc.ibexa.co/en/latest/users/oauth_client/index.md). | Argument | Type | Description | | ---------------- | --------- | ----------------------------------- | | `identifier` | string | Identifier of the OAuth connection. | | `parameters` | `array` | Route parameters. | | `schemeRelative` | `boolean` | Whether to generate a relative URL. | # User Twig functions ### `ibexa_user_get_current()` `ibexa_user_get_current()` returns the User object (`Ibexa\Contracts\Core\Repository\Values\User\User`) of the current user. ``` {{ ibexa_user_get_current().login }} ``` You can get the underlying content item, for example to display the user's last name, by accessing the `content` property: ``` {{ ibexa_render_field(ibexa_user_get_current().content, 'last_name') }} ``` ### `ibexa_current_user()` `ibexa_current_user()` is a deprecated alias of `ibexa_user_get_current()`. ### `ibexa_is_current_user()` The `ibexa_is_current_user()` Twig function checks whether a user is the current repository user. #### Examples ``` {% if ibexa_is_current_user(version_info.author) %} {% endif %} ``` # Twig Components Twig Components are widgets (for example, **My dashboard** blocks from Headless edition) and HTML code (for example, a tag for loading JS or CSS files) that you can inject into the existing templates to customize and extend the user interface. They are combined into groups that are rendered in designated templates. Twig Component groups are available for: - [back office](https://doc.ibexa.co/en/latest/administration/back_office/back_office_elements/custom_components/index.md) - [storefront](https://doc.ibexa.co/en/latest/templating/layout/customize_storefront_layout/index.md) To learn which groups are available in a given view, use the [integration Symfony Profiler](#symfony-profiler-integration). ## Create Twig Component You can create Twig Components in one of two ways: ### PHP code Create a class that implements the `\Ibexa\Contracts\TwigComponents\ComponentInterface` interface. Register it as a service by using the `AsTwigComponent` attribute or the `ibexa.twig.component` service tag: **PHP Attribute** ``` ` tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/script) | `script` | | [Stylesheet](https://github.com/ibexa/twig-components/blob/main/src/lib/Component/LinkComponent.php) | Renders a [`` tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/link) | `stylesheet` | | [Template](https://github.com/ibexa/twig-components/blob/main/src/lib/Component/TemplateComponent.php) | Renders a Twig template | `template` | For the menu component, the following properties are available: | parameter | type | required | description | | --------- | -------- | -------- | ------------------------------ | | name | string | yes | Menu name | | options | array | no | Options passed to menu builder | | path | string[] | no | Path to starting node | | template | string | no | Template used to render menu | | depth | int | no | Menu depth limit | The menu component, same as [back office menus](https://doc.ibexa.co/en/latest/administration/back_office/back_office_menus/back_office_menus/index.md), relies on the [KnpMenuBundle](https://symfony.com/bundles/KnpMenuBundle/current/index.html). For more information about the available properties, refer to the [official documentation of the bundle](https://symfony.com/bundles/KnpMenuBundle/current/index.html#create-your-first-menu). ## Example The following example shows how you can use each of the built-in components to customize the back office: ``` ibexa_twig_components: admin-ui-user-menu: custom-controller-component: type: controller arguments: controller: '\App\Controller\MyController::requestAction' parameters: parameter1: 'custom' parameter2: true custom-html-component: type: html priority: 0 arguments: content: 'Hello world!' admin-ui-user-menu: duplicated_user_menu: type: menu arguments: name: ezplatform_admin_ui.menu.user template: '@ibexadesign/ui/menu/user.html.twig' depth: 1 admin-ui-script-head: custom-script-component: type: script arguments: src: 'https://doc.ibexa.co/en/latest/js/custom.js' crossorigin: anonymous defer: false async: true integrity: sha384-Ewi2bBDtPbbu4/+fs8sIbBJ3zVl0LDOSznfhFR/JBK+SzggdRdX8XQKauWmI9HH2 type: text/javascript admin-ui-stylesheet-head: custom-link-component: type: stylesheet arguments: href: 'https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,700,700i%7CRoboto+Mono:400,400i,700,700i&display=fallback' rel: stylesheet crossorigin: anonymous integrity: sha384-LN/mLhO/GN6Ge8ZPvI7uRsZpiXmtSkep+aFlJcHa8by4TvA34o1am9sa88eUzKTD type: text/css admin-ui-global-search: custom-template-component: type: template priority: 50 arguments: template: '@ibexadesign/ui/component/user_thumbnail/user_thumbnail.html.twig' parameters: user_content: name: "Thumbnail" thumbnail: resource: https://placecats.com/100/100 ``` ## Render Twig Components Render both single Twig Components and whole groups using the [dedicated Twig functions](https://doc.ibexa.co/en/latest/templating/twig_function_reference/component_twig_functions/index.md). You can modify the Component rendering process by: - listening to one of the [related events](https://doc.ibexa.co/en/latest/api/event_reference/twig_component_events/index.md) - decorating the `\Ibexa\Contracts\TwigComponents\Renderer\RendererInterface` service ## Symfony Profiler integration Use the built-in integration with [Symfony Profiler](https://symfony.com/doc/7.4/profiler.html) to see which Twig Components have been rendered in a given view. In the **Ibexa DXP** tab you can find: - the list of all rendered Twig Component groups by the given view, including empty groups - the list of rendered Twig Components with information about the group they belong to *[Image: Symfony Profiler showing the list of rendered Twig Components in a back office view]* # URLs and routes To link to a [Location](https://doc.ibexa.co/en/latest/content_management/locations/index.md) or [Content item](https://doc.ibexa.co/en/latest/content_management/content_model/#content-items), use the [`ibexa_path()`](https://doc.ibexa.co/en/latest/templating/twig_function_reference/url_twig_functions/#ibexa_path) Twig function. You need to provide the function with a location, content, ContentInfo, or [RouteReference](#routereference) object: ```

    Location

    Content Info

    ``` Use [`ibexa_url()`](https://doc.ibexa.co/en/latest/templating/twig_function_reference/url_twig_functions/#ibexa_url) to get an absolute URL to a content item or location: ```

    Location

    ``` ## RouteReference You can use the [`ibexa_route()`](https://doc.ibexa.co/en/latest/templating/twig_function_reference/url_twig_functions/#ibexa_route) Twig function to create a RouteReference object based on the provided information. A RouteReference contains a route with its parameters and can be modified after it's created. Here, the route is based on the ID of the location. ``` {% set routeReference = ibexa_route("ibexa.url.alias", { 'locationId': 2 }) %}

    Route

    ``` A route can also be based on the ID of the content item. The resulting link points to the content item's main location. ``` {% set routeReference = ibexa_route("ibexa.url.alias", { 'contentId': 456 }) %}

    Route

    ``` For cross-SiteAccess links, you can pass the parameter `siteaccess` with a SiteAccess identifier. ``` {% set routeReference = ibexa_route("ibexa.url.alias", { 'contentId': 456, 'siteaccess': 'shop' }) %}

    Route

    ``` With `ibexa_route()` you can modify the route contained in RouteReference after creation, for example, by providing additional parameters: ``` {% set routeReference = ibexa_route("ibexa.url.alias", { 'locationId': 2 }) %} {% do routeReference.set("param", "param-value") %} ``` You can also use `ibexa_route()` to create links to predefined routes, such as the `ibexa.search` route that leads to a search form page: ``` Search ``` ## File download links To provide a download link for a file, use `ibexa_route()` with the `ibexa_content_download` route: ``` {% set download_route = ibexa_route('ibexa_content_download', { 'content': file, 'fieldIdentifier': 'file', }) %} Download ``` ## Route list The following built-in routes are available for the front of the website. > **Tip: Tip** > > To view all routes existing in the system, including internal and back office related ones, run: > > ``` > php bin/console debug:router > ``` ### Registration | Route name | Path | Description | | --------------------------------------------------------------------------- | --------------------------------------------- | ----------------------------------------- | | `ibexa.user.user_register` | `/user/register` | User registration form | | `ibexa.user.register_confirmation`, `ibexa.user.user_register_confirmation` | `/register-confirm`, `/user/register-confirm` | Confirmation page after user registration | ### Login | Route name | Path | Description | | ------------------------------------------ | ---------------------------- | -------------------------------------------------------------------------------------- | | `login` | `/login` | [Login form](https://doc.ibexa.co/en/latest/templating/layout/add_login_form/index.md) | | `logout`, `ibexa.commerce.customer.logout` | `/logout`, `/profile/logout` | Logging out the current user | ### Profile | Route name | Path | Description | | ---------------------------------- | ----------------------- | ----------------- | | `ibexa.commerce.customer.detail` | `/profile` | User profile | | `ibexa.commerce.address.book.list` | `/profile/address_book` | User address book | ### Password | Route name | Path | Description | | ---------------------------------------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | | `ibexa.user_profile.change_password`, `ibexa.commerce.password_change` | `/user/change-password`, `/change_password` | Form for password change | | `ibexa.user.forgot_password` | `/user/forgot-password` | [Form for password resetting](https://doc.ibexa.co/en/latest/templating/layout/add_forgot_password_option/index.md) | | `ibexa.user.forgot_password.migration` | `/user/forgot-password/migration` | Form for resetting password after expiration | | `ibexa.user.forgot_password.login` | `/user/forgot-password/login` | Form for resetting password based on login instead of email address | | `ibexa.user.reset_password` | `/user/reset-password/{hashKey}` | Form for resetting password based on a generated link | ### Content | Route name | Path | Description | | ------------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `ibexa_content_download` | `/content/download/{contentId}/{fieldIdentifier}/{filename}` | Downloading a binary file | | `ibexa.content.create_no_draft` | `/content/create/nodraft/{contentTypeIdentifier}/{language}/{parentLocationId}` | [Creating a content item without using a draft](https://doc.ibexa.co/en/latest/content_management/user_generated_content/#creating-a-content-item-without-using-a-draft) | | `ibexa.content.draft.edit` | `/content/edit/draft/{contentId}/{versionNo}/{language}/{locationId}` | [Editing a content item](https://doc.ibexa.co/en/latest/content_management/user_generated_content/#editing-a-content-item) | | `ibexa.content.draft.create` | `/content/create/draft/{contentId}/{fromVersionNo}/{fromLanguage}` | [Creating a new draft](https://doc.ibexa.co/en/latest/content_management/user_generated_content/#creating-a-new-draft) | ### Search | Route name | Path | Description | | -------------- | --------- | ----------- | | `ibexa.search` | `/search` | Search form | # Custom breadcrumbs ## Breadcrumbs for custom routes To configure breadcrumbs for a custom route you need to configure `breadcrumb_path` and `breadcrumb_names`: ``` custom_blog_index: path: /blog/index defaults: _controller: App\Controller\BlogController::indexAction breadcrumb_path: custom_blog_index breadcrumb_names: Blog List ``` Both `breadcrumb_path` and `breadcrumb_names` must be configured for the breadcrumbs to render correctly. | Option | Description | | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `breadcrumb_path` | Valid route identifier which exists in at least one of the routing YAML files. | | `breadcrumb_names` | Name for the breadcrumb element. If the translation isn't set, there is a fallback to route translation.In the example above if the `Blog List` key has no translation, the fallback key is \`custom_blog_index | ### Multi-part routes If you want breadcrumbs to have more than one part, you can specify more paths and names with the `/` delimiter. Both `breadcrumb_path` and `breadcrumb_names` must contain two parts. In the example below breadcrumbs are generated with two elements (Profile and Blog list): ``` custom_blog_index: path: /blog/index defaults: _controller: App\Controller\BlogController::indexAction breadcrumb_path: blog/custom_blog_index breadcrumb_names: Profile/Blog List ``` > **Note: Restricting HTTP methods** > > When using breadcrumbs for custom routes you cannot restrict the HTTP method for the controller in the routing file. > > To see the correct breadcrumb, you have to check the method in the controller itself: > > ``` > if ($request->getMethod() != REQUEST::METHOD_POST) { > throw new NotFoundHttpException(); > } > ``` ## Custom breadcrumb generator To create a custom breadcrumb generator you have to write a generator class and register it as a service tagged as `ibexa.commerce.breadcrumbs.generator`. The generator must implement `BreadcrumbsGeneratorInterface` and its two methods. You can use `AbstractWhiteOctoberBreadcrumbsGenerator` which implements this interface and provides access to the WhiteOctober breadcrumbs library. Every breadcrumb generator has to add a `translationParameters` array with `type`, `identifier` and `content_type_id`. Always create all three keys and leave the elements empty if not needed. If you can't or don't want to use `AbstractWhiteOctoberBreadcrumbsGenerator`, your generator's `renderBreadcrumbs()` method must handle rendering the HTML code for the breadcrumbs. The highest priority generator which matches `canRender()` renders the breadcrumbs for the current request. # Design engine You can use multiple different designs (theme lists) in your installation. You can set up different designs per SiteAccess or SiteAccess group. Designs are configured under the `ibexa_design_engine.design_list` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa_design_engine: design_list: my_design: [theme2, theme1, theme0] another_design: [theme3, theme0] ``` To indicate when to use a design, configure it under `ibexa.system.`: ``` ibexa: system: : design: my_design ``` Each scope can use only one design. ## Design theme list A theme is a set of directories to look for templates in. At application level, theme's templates are placed in a directory under `templates/themes` which has the same name as the theme. For example, templates placed in `templates/themes/standard` directory are automatically added to the `standard` theme. > **Caution: Caution** > > After you create a new directory with a theme in `templates/themes`, you must clear the cache (`php bin/console cache:clear`), even if you work in the dev environment. The order of themes in a design is important. The design engine attempts to apply the first theme in configuration (for example, `theme2`). If it cannot find the required template or asset in this theme, it proceeds to the next theme in the list (for example, `theme1` then `theme0` and finally the default `standard`). The `@ibexadesign` keyword in template paths is the way to use this feature. When the design engine finds `@ibexadesign`, it loops over the theme list of the current design and checks whether the template for the given theme exists in its paths. For example, `@ibexadesign/pagelayout.html.twig` means that this template is searched at locations like `templates/themes/theme2/pagelayout.html.twig`, `templates/themes/theme1/pagelayout.html.twig`, `templates/themes/theme0/pagelayout.html.twig` and then `templates/themes/standard/pagelayout.html.twig`. You can use this behavior to override only some templates from the main theme of your website. Do this, for example, when you create a SiteAccess with a special design for a campaign. > **Tip: Tip** > > You can check the final design theme lists with the following command: > > ``` > php bin/console debug:container --parameter=ibexa.design.list --format=json > ``` ## Additional configuration ### Additional theme paths You can add any Twig template directory to the theme configuration. You can use it if you want to define templates from third-party bundles as part of one of your themes. To do it, set the `ibexadesign.templates_theme_paths` parameter: ``` ibexa_design_engine: design_list: my_design: [my_theme] templates_theme_paths: my_theme: - '%kernel.project_dir%/vendor///Resources/views' ``` Theme directories that you define have priority over the ones defined in `templates_theme_paths`. This ensures that it's always possible to override a template at the application level. You can also add a global override directory, by listing paths without assigning them to a theme: ``` ibexa_design_engine: templates_override_paths: - '%kernel.project_dir%/src/' ``` > **Tip: Tip** > > You can check the final template directory list per theme with the following command: > > ``` > php bin/console debug:container --parameter=ibexa.design.templates.path_map --format=json > ``` > > `_override` is a theme added at the beginning of the current design theme list at template path resolution time. ### Asset resolution In production environments, to improve performance, asset resolution is done at compilation time. In development environments, assets are resolved at runtime. You can change this behavior by setting `disable_assets_pre_resolution`: ``` ibexa_design_engine: disable_assets_pre_resolution: true ``` # Add new design To create different designs for different version of the website, you configure different sites based on the [SiteAccess](https://doc.ibexa.co/en/latest/multisite/multisite/index.md) content. This example shows how to prepare a site for a "Summer Sale" marketing campaign and provide it with a distinct design. ## Configure a new SiteAccess First, in the SiteAccess configuration, under the `ibexa.siteaccess` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files), add the `campaign` SiteAccess: ``` ibexa: siteaccess: list: - import - site - admin - corporate - campaign groups: site_group: [import, site, campaign] storefront_group: [site] corporate_group: [corporate] default_siteaccess: site ``` Adding the `campaign` SiteAccess to [`site_group`](https://doc.ibexa.co/en/latest/multisite/multisite_configuration/#siteaccess-groups) enables you to add common configuration for both SiteAccesses at the same time. > **Tip: Tip** > > For details about configuring different site roots and matching SiteAccesses, see [Set up campaign SiteAccess](https://doc.ibexa.co/en/latest/multisite/set_up_campaign_siteaccess/index.md). ## Add themes Next, configure a new `summersale` design for this theme, also named `summersale`: ``` ibexa_design_engine: design_list: summersale: [summersale] ``` Notice that the `standard` theme is automatically added at the end of the `summersale` design's theme list. Ensure that the `campaign` site uses this design (while the default `site` uses the default `standard` design). ``` ibexa: system: campaign: languages: [eng-GB] design: summersale site: languages: [eng-GB] design: standard ``` ## Add templates Now, create templates for the two sites. Templates for the main site should be placed in `templates/themes/standard`, and templates for the campaign site in `templates/themes/summersale`. First, modify the built-in general [page layout](https://doc.ibexa.co/en/latest/templating/templates/template_configuration/#page-layout) `templates/themes/standard/pagelayout.html.twig` by including a header and a footer section: ``` {% include '@ibexadesign/parts/header.html.twig' %} {% block content %} {% endblock %} {% include '@ibexadesign/parts/footer.html.twig' %} {% block javascripts %} ``` `@ibexadesign` in the template paths points to a template relevant for the current design. In case of `site`, the template used for the header is `templates/themes/standard/parts/header.html.twig`. Create both the header and the footer template, for example: ``` ``` ```
    Copyright Acme SA
    ``` Now, create templates for content, for example for an article, that [extend the page layout](https://doc.ibexa.co/en/latest/templating/templates/templates/#connecting-templates): ``` {% extends '@ibexadesign/pagelayout.html.twig' %} {% block content %} {% endblock %} ``` Configure the content view so that both sites, the main one and the campaign, use this template. To do it, use the `site_group` that both sites belong to: ``` ibexa: system: site_group: content_view: full: article: template: '@ibexadesign/full/article.html.twig' match: Identifier\ContentType: [ article ] ``` Now, create an Article content item and preview it on the front page. You should see the article with a header and footer that you defined for the main site. ## Override templates Now, you need to override the header of the site to fit the campaign. Create a separate `templates/themes/summersale/parts/header.html.twig` file with different content, for example: ``` ``` Preview the Article through the `campaign` SiteAccess: `/campaign/`. You can see that the page uses the campaign header, while the rest of the layout, including the footer, is the same as in the main site. This is because you defined `standard` design as fallback for this SiteAccess: ``` ibexa_design_engine: design_list: summersale: [summersale] ``` In this case, if the design engine cannot find a template for the current design, it uses the template from the next configured design. In the case above, the engine doesn't find the footer template for the `campaign` SiteAccess, so it uses the one from `standard`. This way you don't need to provide all templates for a new design, but only those that you want to be different than the fallback one. # Content queries With content queries you can find and render specific content according to criteria that you define. You can use queries to list or embed content items, such as: - [children in a folder](https://doc.ibexa.co/en/latest/templating/embed_and_list_content/list_content/#list-children-with-query-type) - related articles - [most recent blog posts](https://doc.ibexa.co/en/latest/templating/queries_and_controllers/create_custom_query_type/index.md) - recommended products Content queries use the built-in Query controller which simplifies querying. For more complex cases, you can build custom [controllers](https://doc.ibexa.co/en/latest/templating/queries_and_controllers/controllers/index.md). ## Query types The Query controller offers a set of [built-in Query types](https://doc.ibexa.co/en/latest/templating/queries_and_controllers/built-in_query_types/index.md). You can use them in the content view configuration, or in the [content query field](#content-query-field). You can also write [custom Query types](https://doc.ibexa.co/en/latest/templating/queries_and_controllers/create_custom_query_type/index.md) for the cases that aren't covered by the built-in ones. ### Query type configuration To use a Query type, select the Query controller (`ibexa_query`) in the [content view configuration](https://doc.ibexa.co/en/latest/templating/templates/template_configuration/index.md) and select the Query type under `params.query.query_type`: ``` folder: controller: ibexa_query::contentQueryAction template: '@ibexadesign/full/folder.html.twig' params: query: query_type: 'Children' parameters: content: '@=content' assign_results_to: items match: Identifier\ContentType: folder ``` Use one of the following Query controller methods: - `locationQueryAction` runs a location Search - `contentQueryAction` runs a content Search - `contentInfoQueryAction` runs a ContentInfo search - `pagingQueryAction` returns a `PagerFanta` object and can be used to quickly [paginate query results](#pagination) See the [Search](https://doc.ibexa.co/en/latest/search/search/index.md) documentation page for more details about different types of search. All Query types take the following parameters: - `query_type` is the name of the Query type to use. - `parameters` can include: - arbitrary values - expressions based on the `content`, `location` and `view` variables. For example, `@=location.id` is evaluated to the current Location's ID. - `assign_results_to` declares the Twig variable that contains the search results. > **Tip: Tip** > > Search results are a `SearchResult` object, which contains `SearchHit` objects. To get the content or Locations that are in search results, you access the `valueObject` of the `SearchHit`. ### Pagination To paginate the results of a query, use the `pagingQueryAction` of the Query controller and assign a limit per page in `params.query.limit`: ``` content_view: full: folder: controller: ibexa_query::pagingQueryAction template: '@ibexadesign/full/folder.html.twig' params: query: query_type: 'Children' parameters: content: '@=content' assign_results_to: items limit: 3 match: Identifier\ContentType: folder ``` Use the [`pagerfanta`](https://www.babdev.com/open-source/packages/pagerfanta/docs/3.x/intro) function to render pagination controls: ``` {% for item in items %} {{ ibexa_render(item.valueObject) }} {% endfor %} {{ pagerfanta(items, 'twitter_bootstrap5', { 'routeName': 'ibexa.url.alias', 'routeParams': {'location': location } }) }} ``` ## Content query field The [Content query field](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/contentqueryfield/index.md) is a field that defines a query. The results of the query are available in the field value. *[Image: Content query field definition]* ### Query type When adding the field to a content type definition, select the Query type in the **Query type** dropdown. All Query types in the application are available, both [built-in](https://doc.ibexa.co/en/latest/templating/queries_and_controllers/built-in_query_types/index.md) and [custom ones](https://doc.ibexa.co/en/latest/templating/queries_and_controllers/create_custom_query_type/index.md). ### Returned types Select the content type of items you want to return in the **Returned type** dropdown. To take it into account, your Query type must filter on the content type. Provide the selected content type through the `returnedType` variable: ``` contentType: '@=returnedType' ``` ### Pagination Select **Enable pagination** and set the number of items per page to paginate the results. You can override the pagination settings from field definition by setting the `enablePagination`, `disablePagination` or `itemsPerPage` parameters when rendering the content query field: ``` {{ ibexa_render_field(content, 'query', { location: location|default(null), 'parameters': { 'enablePagination': true, 'itemsPerPage': 8 } }) }} ``` You can also define an offset for the results. Provide the offset in the Query type, or in parameters: ``` offset: 3 ``` If pagination is disabled and an offset value is defined, the query's offset is added to the offset calculated for a page. For example, with `offset` 5 and `itemsPerPage` 10, the first page starts with 5, the second page starts with 15, and so on. Without offset defined, pagination defines the starting number for each page. For example, with `itemsPerPage` 10, first page starts with 0, second page starts with 10, and so on. ### Parameters The following variables are available in parameter expressions: - `returnedType` - the identifier of the content type selected in the **Returned type** dropdown - `content` - the current content item - `location` - the current Location of the content item - `mainLocation` - the main Location of the content item - `contentInfo` - the current content item's ContentInfo ### Content view configuration To render a content query field, in the content view configuration, use the `content_query_field` view type: ``` content_view: content_query_field: blog: template: '@ibexadesign/content_query/blog_posts.html.twig' match: Identifier\ContentType: blog '@Ibexa\FieldTypeQuery\ContentView\FieldDefinitionIdentifierMatcher': query ``` The identifier of the content query field must be matched by using the `'@Ibexa\FieldTypeQuery\ContentView\FieldDefinitionIdentifierMatcher'` matcher. Query results are provided to the template in the `items` variable. See [List content](https://doc.ibexa.co/en/latest/templating/embed_and_list_content/list_content/#list-children-in-content-query-field) for an example of using the content query field. # Built-in Query types ## General Query type parameters All built-in Query types take the following optional parameters: - `limit` - maximum number of results to return - `offset` - offset for search hits, used for paginating the results - `sort` - [sort order](#sort-order) - `filter` - additional query filters: - `content_type` - return only results of given content types - `visible_only` - return only visible results (default `true`) - `siteaccess_aware` - return only results limited to the current SiteAccess root (default `true`) For example: ``` params: query: query_type: 'Children' parameters: content: '@=content' filter: content_type: ['blog_post'] visible_only: false limit: 5 offset: 2 sort: 'content_name asc, date_published desc' assign_results_to: items ``` ### Sort order To provide a sort order to the `sort` parameter, use names of the Sort Clauses. To find them, refer to [Sort Clause](https://doc.ibexa.co/en/latest/search/sort_clause_reference/sort_clause_reference/index.md) and the [relevant Sort Clause class](https://github.com/ibexa/core/blob/main/src/bundle/Core/Resources/config/sort_spec.yml#L29) ## Children The `Children` Query type retrieves children of the given location. It takes `location` or `content` as parameters. ``` params: query: query_type: 'Children' parameters: content: '@=content' assign_results_to: items ``` > **Tip: Tip** > > For an example of using the `Children` Query type, see [List content](https://doc.ibexa.co/en/latest/templating/embed_and_list_content/list_content/#list-children-with-query-type). ## Siblings The `Siblings` Query type retrieves locations that have the same parent as the provided content item or location. It takes `location` or `content` as parameters. ``` params: query: query_type: 'Siblings' parameters: content: '@=content' assign_results_to: items ``` > **Tip: Tip** > > For an example of using the `Siblings` Query type, see [Embed related content](https://doc.ibexa.co/en/latest/templating/embed_and_list_content/embed_content/#embed-siblings-with-query-type). ## Ancestors The `Ancestors` Query type retrieves all ancestors (direct parents and their parents) of the provided location. It takes `location` or `content` as parameters. ``` params: query: query_type: 'Ancestors' parameters: content: '@=content' assign_results_to: items ``` ## RelatedToContent The `RelatedToContent` Query type retrieves content that is a reverse relation to the provided content item. > **Tip: Tip** > > Reverse relations mean that the Query type shows content items that are *related to* the provided content item. For example, if a blog post contains a link to an article, you can use a `RelatedToContent` query to find the blog post from the article. To find all relations of a content item (in this example, all content that the blog post is related to), refer to [Embed content](https://doc.ibexa.co/en/latest/templating/embed_and_list_content/embed_content/#embed-relations-with-a-custom-controller). It takes `content` or `field` as required parameters. `field` indicates the Relation or RelationList field that contains the relations. ``` params: query: query_type: 'RelatedToContent' parameters: content: '@=content' field: 'relations' assign_results_to: items ``` ## GeoLocation The `GeoLocation` Query type retrieves content by distance of the location provided in a MapLocation field. It takes the following parameters: - `field` - MapLocation field identifier - `distance` - distance to check for - `latitude` and `longitude` - coordinates of the location to check distance to - (optional) `operator` - operator to check value against, by default `<=` ``` params: query: query_type: 'GeoLocation' parameters: field: 'location' distance: 200 latitude: '@=content.getFieldValue("location").latitude' longitude: '@=content.getFieldValue("location").longitude' operator: '<' assign_results_to: items ``` ## Catalog The `Catalog` Query type retrieves products belonging to a [catalog](https://doc.ibexa.co/en/latest/product_catalog/catalogs/index.md). It takes the following parameters: - `identifier` - identifier of the catalog ``` params: query: query_type: 'Catalog' parameters: identifier: 'promo' assign_results_to: products ``` # Create a custom Query type If you need to perform a more complex query than the [built-in Query types](https://doc.ibexa.co/en/latest/templating/queries_and_controllers/built-in_query_types/index.md) allow, you can create a custom Query type. The following example shows how to create a custom Query type that renders the latest content items of selected Types. First, add the following `LatestContentQueryType.php` file to `src/QueryType`: ``` new Query\Criterion\LogicalAnd($criteria), 'sortClauses' => [ new Query\SortClause\DatePublished(Query::SORT_DESC), ], 'limit' => $parameters['limit'] ?? 10, ]); } public function getSupportedParameters() { return ['contentType', 'limit']; } } ``` > **Tip: Tip** > > When the custom Query type is in the `App` namespace, like in the example above, it's registered automatically as a service. Otherwise, register it with the `ibexa.query_type` service tag. The name defined in `getName()` is the one you use to identify the Query type in content view configuration. ``` public static function getName() { return 'LatestContent'; } ``` > **Caution: Caution** > > Query type name must be unique. The `getQuery()` method constructs the query based on Search Criteria and Sort Clauses. For more information, see [Content search](https://doc.ibexa.co/en/latest/search/search_api/index.md) and [Search reference](https://doc.ibexa.co/en/latest/search/criteria_reference/search_criteria_reference/index.md). The `getSupportedParameters()` method provides the parameters you can set in content view configuration. ``` public function getSupportedParameters() { return ['contentType', 'limit']; } ``` > **Note: Note** > > To have more control over the details of parameters, use the [Options resolver-based Query type](#options-resolver-based-query-type). Then, in the content view configuration, indicate that the content view should use the custom Query type: ``` content_view: full: latest: controller: ibexa_query::locationQueryAction template: '@ibexadesign/full/latest.html.twig' match: Identifier\ContentType: "latest" params: query: query_type: LatestContent parameters: contentType: [article, blog_post] assign_results_to: latest ``` ## Options resolver-based Query type Additionally, your custom Query type can extend the `OptionsResolverBasedQueryType` abstract class. This gives you more flexibility when defining parameters. In the `configureOptions()` method you can define the allowed parameters, their types and default values. ``` new Query\Criterion\LogicalAnd($criteria), 'sortClauses' => [ new Query\SortClause\DatePublished(Query::SORT_DESC), ], 'limit' => $parameters['limit'] ?? 10, ]); } protected function configureOptions(OptionsResolver $resolver): void { $resolver->setDefined(['contentType', 'limit']); $resolver->setAllowedTypes('contentType', 'array'); $resolver->setAllowedTypes('limit', 'int'); $resolver->setDefault('limit', 10); } } ``` > **Note: Note** > > In contrast with the previous example, a Query type that extends `OptionsResolverBasedQueryType` must implement the `doGetQuery()` method instead of `getQuery()`. # Controllers By configuring a controller you can modify and enhance the way in which the built-in content view controller renders content. You indicate which controller to use in the [content view configuration](https://doc.ibexa.co/en/latest/templating/templates/template_configuration/index.md), under the `controller` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` article: controller: App\Controller\RelationController::showContentAction template: '@ibexadesign/full/article.html.twig' match: Identifier\ContentType: article ``` ``` **Tip: Permissions for custom controllers** > > See [permission documentation](https://doc.ibexa.co/en/latest/permissions/permission_overview/#permissions-for-custom-controllers) for information about access control for custom controllers. # List content To render a list of content items, for example, content in a folder, or blog posts in a blog, you can use one of two methods: - use a [Query type](#list-children-with-query-type) - create a content type with a [Content Query Field](#list-children-in-content-query-field) ## List children with Query type The following example shows how to render the children of a Folder. First, in the [content view configuration](https://doc.ibexa.co/en/latest/templating/templates/template_configuration/index.md), add the following view for the Folder content type: ``` content_view: full: folder: controller: ibexa_query::contentQueryAction template: '@ibexadesign/full/folder.html.twig' params: query: query_type: 'Children' parameters: content: '@=content' assign_results_to: items limit: 3 match: Identifier\ContentType: folder ``` `controller` defines which controller is used to render the view. In this example, it's the default [Query controller](https://doc.ibexa.co/en/latest/templating/queries_and_controllers/content_queries/index.md). ``` controller: ibexa_query::contentQueryAction ``` `params` define that you want to render the content by using the [`Children` Query type](https://doc.ibexa.co/en/latest/templating/queries_and_controllers/built-in_query_types/#children). This Query type automatically finds the children of the current content item. The results of the query are placed in the `items` variable, which you can use in templates. Then, place the following template in `templates/themes//full/folder.html.twig`: ``` {% for item in items.searchHits %} {{ ibexa_render(item.valueObject, {'viewType': 'line'}) }} {% endfor %} ``` This template uses the [`ibexa_render()` Twig function](https://doc.ibexa.co/en/latest/templating/twig_function_reference/content_twig_functions/#ibexa_render) to render every child of the folder with the default template for the `line` view. ## List children in Content query Field A [Content query Field](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/contentqueryfield/index.md) is a field that defines a query. The following example shows how to use a Content query field to render a Blog with its Blog Post children. First, create a Blog content type that contains a Content query field with the identifier `query`. In the Field definition, select "Children" as the Query type. Provide the `content` parameter that the Query type requires: ``` content: '@=content' ``` You can paginate the query results by checking the **Enable pagination** box and selecting a limit of results per page. Select the content type you want to render (in this case, Blog Post) as **Returned type**. Then, in the content view configuration, add the configuration under `content_query_field`: ``` content_view: content_query_field: blog: template: '@ibexadesign/content_query/blog_posts.html.twig' match: Identifier\ContentType: blog '@Ibexa\FieldTypeQuery\ContentView\FieldDefinitionIdentifierMatcher': query ``` The `match` configuration matches both the content type and the identifier of the Content query field. Finally, in the template \`templates/themes/ # Embed related content To embed content in another content item, you query for it in the repository. There are two ways to query for a content item: - by using a [Query type](#embed-siblings-with-query-type) - by writing a [custom controller](#embed-relations-with-a-custom-controller) ## Embed siblings with Query type To render the Siblings of a content item (other content under the same parent Location), use the [Siblings Query type](https://doc.ibexa.co/en/latest/templating/queries_and_controllers/built-in_query_types/#siblings). To do it, use the built-in `ibexa_query` controller's `contentQueryAction`: ``` content_view: full: blog_post: controller: ibexa_query::contentQueryAction template: '@ibexadesign/full/blog_post.html.twig' params: query: query_type: 'Siblings' parameters: content: '@=content' limit: 3 sort: 'date_published desc' assign_results_to: items match: Identifier\ContentType: blog_post ``` The results of the Siblings query are placed in the `items` variable, which you can use in the template: ``` {{ ibexa_content_name(content) }} {% for item in items.searchHits %} {{ ibexa_render(item.valueObject, {'viewType': 'line'}) }} {% endfor %} ``` ## Embed Relations with a custom controller You can use a custom controller for any situation where Query types aren't sufficient. ``` article: controller: App\Controller\RelationController::showContentAction template: '@ibexadesign/full/article.html.twig' params: accepted_content_types: [ 'article', 'test_target', 'test_source' ] match: Identifier\ContentType: article ``` This configuration points to a custom `RelationController` that should render all Articles with the `showContentAction()` method. ``` getParameter('accepted_content_types'); $location = $this->locationService->loadLocation($locationId); $contentInfo = $location->getContentInfo(); $versionInfo = $this->contentService->loadVersionInfo($contentInfo); $relationList = $this->contentService->loadRelationList($versionInfo); $items = []; foreach ($relationList as $relationListItem) { if ($relationListItem->hasRelation() && in_array($relationListItem->getRelation()->getDestinationContentInfo()->getContentType()->identifier, $acceptedContentTypes)) { $items[] = $this->contentService->loadContentByContentInfo($relationListItem->getRelation()->getDestinationContentInfo()); } } $view->addParameters([ 'items' => $items, ]); return $view; } } ``` This controller uses the Public PHP API to get [the Relations of a content item](https://doc.ibexa.co/en/latest/content_management/content_api/browsing_content/#relations) (lines 27-28). The controller takes the custom parameter called `accepted_content_types` (line 23), which is an array of content type identifiers that are rendered. This way you can control which content types you want to show or exclude. Finally, the controller returns the view with the results that were provided in the `items` parameter. You can use this parameter as a variable in the template: ``` {% block content %}

    {{ ibexa_content_name(content) }}

      {% for item in items %} {{ ibexa_render(item, {'viewType': 'embed'} ) }} {% endfor %}
    {% endblock %} ``` # Render images To render images contained in Image Asset or Image fields, use the [`ibexa_render_field()`](https://doc.ibexa.co/en/latest/templating/twig_function_reference/field_twig_functions/#ibexa_render_field) Twig function. ``` {{ ibexa_render_field(content, 'image') }} ``` You can pass the name of an [image variation](#configure-image-variation) as an argument, for example: ``` {{ ibexa_render_field(content, 'image', { 'parameters': { 'alias': 'large' } }) }} ``` ## Render first image If a content item contains more than one image, you may want to select the first filled image to render. This enables you to avoid a situation where, for example, the featured image in an article is missing, because the first image field was left empty. The [`ibexa_content_field_identifier_first_filled_image()`](https://doc.ibexa.co/en/latest/templating/twig_function_reference/image_twig_functions/#ibexa_content_field_identifier_first_filled_image) Twig function returns the identifier of the first image field that isn't empty. ``` {% set firstImage = ibexa_content_field_identifier_first_filled_image(content) %} {{ ibexa_render_field(content, firstImage }} ``` > **Caution: Caution** > > This function works only for [Image](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/imagefield/index.md) fields. It doesn't work for [ImageAsset](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/imageassetfield/index.md) fields. ## Configure image variation The same image can have multiple variations differing in things such as scale, cropping, or applied filters. You can use the built-in image variations or [configure your own](https://doc.ibexa.co/en/latest/templating/image_variations/#custom-image-variations). The following example creates a custom variation that scales the image down to a 200 x 200 thumbnail and renders it in grayscale: ``` ibexa: system: site_group: image_variations: gray_thumb: reference: null filters: geometry/scaledownonly: [200, 200] colorspace/gray: [] ``` To use it, select the variation when rendering the image: ``` {{ ibexa_render_field(content, 'image', { 'parameters': { 'alias': 'gray_thumb' } }) }} ``` ## Use focal point In the [image editor](https://doc.ibexa.co/en/latest/content_management/images/configure_image_editor/index.md) you can define a focal point for an image. The focal point doesn't have an instant effect when you use the default templates. However, you can use it to select the part of the image the view focuses on when the image is cropped. The following example shows how to use an image contained in an Image field as a focussed background. > **Note: Note** > > This implementation is only an example and depends on the JavaScript framework you're using. First, in the main template, render the Image field with a custom template: ``` {{ ibexa_render_field(content, 'image', { 'template': 'fields/image.html.twig' }) }} ``` Then, create the custom Field template in `templates/fields/image.html.twig`, [overriding the default `ezimage_field` template block](https://doc.ibexa.co/en/latest/templating/render_content/render_content/#field-templates): ``` {% block ezimage_field %} {% if field.value.additionalData.focalPointX is defined and field.value.additionalData.focalPointY is defined %} {% set position_x = (field.value.additionalData.focalPointX / field.value.width) * 100 %} {% set position_y = (field.value.additionalData.focalPointY / field.value.height) * 100 %} {% else %} {% set position_x = 50 %} {% set position_y = 50 %} {% endif %} {% set imageAlias = ibexa_image_alias( field, versionInfo, parameters.alias|default( 'original' ) ) %} {% set src = imageAlias ? asset( imageAlias.uri ) : "//:0" %}
    {% endblock %} ``` This template uses the focal point information contained in the image's additional data to position the background so that the focused part of the image is displayed. # Customize storefront layout Editions: Commerce The built-in storefront offers a set of templates covering all functionalities of a shop, divided into smaller components. To customize your shop, you can override either whole templates, or specific components. The built-in templates belong to the `storefront` [theme](https://doc.ibexa.co/en/latest/templating/design_engine/design_engine/index.md). To override any of them, copy its directory structure in your template directory. ## Customize with Twig Components You can customize parts of the storefront by using [Twig components](https://doc.ibexa.co/en/latest/templating/components/index.md). It allows you to inject your own widgets, extending the storefront behavior. The available groups for the storefront are: | Group name | Template file | | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | | `storefront-before-maincart` | `vendor/ibexa/storefront/src/bundle/Resources/views/themes/storefront/cart/component/maincart/maincart.html.twig` | | `storefront-after-maincart` | `vendor/ibexa/storefront/src/bundle/Resources/views/themes/storefront/cart/component/maincart/maincart.html.twig` | | `storefront-before-minicart` | `vendor/ibexa/storefront/src/bundle/Resources/views/themes/storefront/cart/component/minicart/minicart.html.twig` | | `storefront-after-minicart` | `vendor/ibexa/storefront/src/bundle/Resources/views/themes/storefront/cart/component/minicart/minicart.html.twig` | | `storefront-before-add-to-cart` | `vendor/ibexa/storefront/src/bundle/Resources/views/themes/storefront/cart/component/add_to_cart/add_to_cart.html.twig` | | `storefront-after-add-to-cart` | `vendor/ibexa/storefront/src/bundle/Resources/views/themes/storefront/cart/component/add_to_cart/add_to_cart.html.twig` | | `storefront-before-summary` | `vendor/ibexa/storefront/src/bundle/Resources/views/themes/storefront/cart/component/summary/summary.html.twig` | | `storefront-after-summary` | `vendor/ibexa/storefront/src/bundle/Resources/views/themes/storefront/cart/component/summary/summary.html.twig` | ## Template customization example As an example, to change the cart display when it contains no products, you need to override [`vendor/ibexa/storefront/src/bundle/Resources/views/themes/storefront/cart/component/maincart/maincart_empty_cart.html.twig`](https://github.com/ibexa/storefront/blob/main/src/bundle/Resources/views/themes/storefront/cart/component/maincart/maincart_empty_cart.html.twig) template. To do it, create your own template in `templates/theme/storefront/cart/component/maincart/maincart_empty_cart.html.twig`. You can customize it, for example, to remove a "Continue shopping" button in the following way: ``` {% trans_default_domain 'ibexa_cart' %} {% block empty_content %}

    {{ 'cart_view.empty.headline'|trans|desc('Your shopping cart is empty') }}

    {% endblock %} ``` ## Available templates All the storefront templates are located in `vendor/ibexa/storefront/src/bundle/Resources/view/themes/storefront`. The most important templates are: | Template | Component | | ----------------------------- | ----------------------------- | | `storefront/layout.html.twig` | main layout of the storefront | ### User | Template | Component | | ---------------------------------------- | ------------------------------------------------ | | `storefront/security/layout.html.twig` | main layout for the login and registration pages | | `storefront/security/login.html.twig` | user login page | | `storefront/security/register.html.twig` | user registration page | ### General components | Template | Component | | --------------------------------------- | ----------------------- | | `component/logo.html.twig` | shop logo | | `component/region_switcher.html.twig` | switcher for regions | | `component/language_switcher.html.twig` | switcher for regions | | `component/currency_switcher.html.twig` | switcher for currencies | ### Cart | Template | Component | | -------------------------------------------------- | ----------------------------------------------------- | | `cart/component/maincart/maincart.html.twig` | general view of the main cart | | `cart/component/minicart/minicart.html.twig` | minicart (cart icon displayed at the top of the page) | | `cart/component/add_to_cart/add_to_cart.html.twig` | "add to cart" element | | `cart/component/summary/summary.html.twig` | cart summary | | `cart/component/quick_order/quick_order.html.twig` | quick order | #### Extend Twig template ``` {% extends '@IbexaCart/themes/standard/cart/component/minicart/minicart.html.twig' %} {% block content %} {{ 'minicart.icon.alt'|trans|desc('Cart') }} {{ parent() }} {% endblock %} ``` To avoid self-reference, `@IbexaCart` is used instead of `@ibexadesign`. Built-in components aren't styled, so you can freely customize them according to your needs. You can add CSS classes to the base Twig by using attribute objects. For example, to add custom CSS classes to quantity input in the "Add to Cart" component, use the following: ``` {% set quantity_input_attr = { class: 'ibexa-store-input ibexa-store-input--number ibexa-store-add-to-cart__quantity-input', } %} ``` Every element is also inside its own block so you can override the whole block. #### Extending JavaScript In case of the JavaScript component, you should import the original class and extend it: ``` import Minicart from '@ibexa-cart/src/bundle/Resources/public/js/component/minicart'; export default class StorefrontMinicart extends Minicart {} ``` The example below shows how to add a "Clear" button support to the maincart: ``` import Maincart from '@ibexa-cart/src/bundle/Resources/public/js/component/maincart'; export default class StorefrontMaincart extends Maincart { constructor(options) { super(options); this.clearCartBtn = this.container.querySelector('.ibexa-store-maincart__clear-cart-btn'); this.onCartClear = this.onCartClear.bind(this); } attachStorefrontMaincartListeners() { this.clearCartBtn.addEventListener('click', this.onCartClear, false); } onCartClear() { this.cart.empty(); } } ``` Next, add the button in the Twig file. ### Main cart You must customize the base widget for the main cart view, because out-of-the-box it consists only of the container with items. Each item consists of `
    ` wrappers with quantity input and remove item button. With customization you can add layout containers and items' data such as title or price. Available Twigs: - `@IbexaCart/themes/standard/cart/component/maincart/maincart.html.twig` with parameters: - `attr` - `item_template_attr` - `items_container_attr` - `item_template_params` - `item_template_path` - `net_price_template` - `@IbexaCart/themes/standard/cart/component/maincart/maincart_item.html.twig` with parameters: - `cart_entry_quantity` - `item_attr` - `quantity_input_attr` - `remove_item_btn_attr` JavaScript class: - `@ibexa-cart/src/bundle/Resources/public/js/component/maincart` ### Add to Cart You could extend this widget by adding variant selectors. Available Twig: - `@IbexaCart/themes/standard/cart/component/add_to_cart/add_to_cart.html.twig` with parameters: - `is_disabled` - `attr` - `product_code` - `quantity_input_attr` - `add_to_cart_btn_attr` JavaScript class: - `@ibexa-cart/src/bundle/Resources/public/js/component/summary` ### Minicart You could modify the minicart widget by changing its icon, title or other elements. Available Twig: - `@IbexaCart/themes/standard/cart/component/minicart/minicart.html.twig` with parameters: - `count` - `attr` - `counter_attr` ### Checkout | Template | Component | | -------------------------------------------- | ------------------------ | | `checkout/layout.html.twig` | main checkout layout | | `checkout/component/step.html.twig` | individual checkout step | | `checkout/component/quick_summary.html.twig` | checkout summary | > **Tip: Tip** > > For templates related to product rendering, see [Customize product view](https://doc.ibexa.co/en/latest/templating/render_content/customize_product_view/#available-templates). ### Summary You could extend the summary widget to let buyers navigate from this view, for example, to checkout, or back to shopping, by adding respective buttons. | Template | Component | | ----------------------------------------------- | ------------------- | | `cart/component/summary/summary.html.twig` | main summary layout | | `cart/component/summary/summary_item.html.twig` | item summary layout | ### Quick order You can modify the quick order page by changing its form, title or other elements. Available Twigs: - `@IbexaCart/themes/standard/cart/component/quick_order/quick_order.html.twig` with parameters: - `form_themes` - `form_start_attr` - `form_start_vars` - `main_widget_vars` - `add_to_cart_btn_attr` - `form_end_vars` - `@IbexaCart/themes/standard/cart/component/quick_order/quick_order_form_fields.html.twig` with parameters per block, block's names are generated based on fields in Symfony form: - `quick_order_widget` block - `main_attr` - `widget_attr` - `quick_order_file_row` block - `file_attr` - `file_vars` - `quick_order_entries_row` block - `entries_wrapper_attr` - `add_entry_btn_attr` - `quick_order_entries_widget` block - `entries_attr` - `form_widget_vars` - `quick_order_entry_row` block - `entry_attr` - `delete_entry_btn_attr` - `code_attr` - `code_vars` - `quantity_attr` - `quantity_vars` - `errors_vars` JavaScript class: - `@ibexa-cart/src/bundle/Resources/public/js/component/quick.order` # Add breadcrumbs To add breadcrumbs to your website, first prepare a general layout template in a `templates/themes//pagelayout.html.twig` file. This template can contain things such as header, menu, footer, and [assets](https://doc.ibexa.co/en/latest/templating/assets/index.md) for the whole site, and all other templates [extend](https://doc.ibexa.co/en/latest/templating/templates/templates/#connecting-templates) it. Then, to render breadcrumbs, create a `BreadcrumbController.php` file in `src/Controller`: ``` query = new Criterion\Ancestor([$this->locationService->loadLocation($locationId)->pathString]); $results = $this->searchService->findLocations($query); $breadcrumbs = []; foreach ($results->searchHits as $searchHit) { $breadcrumbs[] = $searchHit; } return $this->render( '@ibexadesign/parts/breadcrumbs.html.twig', [ 'breadcrumbs' => $breadcrumbs, ] ); } } ``` The controller uses the [Ancestor Search Criterion](https://doc.ibexa.co/en/latest/search/criteria_reference/ancestor_criterion/index.md) to find all Ancestors of the current Location (line 27). It then places the ancestors in the `breadcrumbs` variable that you can use in the template. Next, call this controller from the page layout template and pass the current location ID as a parameter: ``` {{ render( controller( "App\\Controller\\BreadcrumbController::showBreadcrumbsAction", { 'locationId': locationId, } ) ) }} ``` Finally, create a breadcrumb template in `templates/themes//parts/breadcrumbs.html.twig`, as indicated in the controller (line 34). In this template, iterate over all breadcrumbs and render links to them: ``` {% for breadcrumb in breadcrumbs %} {% if not loop.first %} -> {% endif %} {% if not loop.last %} {{ breadcrumb.valueObject.contentInfo.name }} {% else %} {{ breadcrumb.valueObject.contentInfo.name }} {% endif %} {% endfor %} ``` # Add "forgot password" option The "forgot password" option allows users of a specific SiteAccess, admin or front, to request a password change. You can customize the template used in the `/user/forgot-password` route. Follow the instructions to create and configure a "forgot password" form. Add the following configuration files under the `ibexa.system..user_forgot_password` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa: system: : user_forgot_password: templates: form: mail: ``` Under the `templates` key, provide the path to templates responsible for rendering the forgot password form (`form`) and email (`mail`), which users receive after they request a password change. The [default templates](https://github.com/ibexa/user/tree/main/src/bundle/Resources/views) for forgot password form and email are located in `ibexa/user/src/bundle/Resources/views`. The [templates](https://github.com/ibexa/admin-ui/tree/main/src/bundle/Resources/views/themes/admin/account/forgot_password) specific for the back office are in `ibexa/admin-ui/src/bundle/Resources/views/themes/admin/account`. You can also modify [other user management templates](https://doc.ibexa.co/en/latest/users/user_registration/#other-user-management-templates). To add a link redirecting to the reset password form, in the page layout template, provide the following code: ``` {{ 'authentication.forgot_password'|trans|desc('Forgot password?') }} ``` You can customize the layout of templates according to your needs. For more information, see [Template documentation](https://doc.ibexa.co/en/latest/templating/templates/templates/index.md). # Add login form You can create a login form for your users. Follow the instruction below to create a template with login form. If you want to configure more options, for example, password expiration, see [other user management templates](https://doc.ibexa.co/en/latest/users/user_registration/#other-user-management-templates). First, make sure you have configured [login methods](https://doc.ibexa.co/en/latest/users/login_methods/index.md). If you only want to change a template, add the following configuration under the `ibexa.system..user` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa: system: my_siteaccess: user: login_template: '@ibexadesign/Security/login.html.twig' ``` To add a link redirecting to the login form, in the page layout template, provide the following code: ``` Log in ``` Next, add the template defined in the event. In `templates/themes//login`, create an `expired_credentials.html.twig` file: ``` {% extends '@ibexadesign/Security/base.html.twig' %} {%- block content -%}

    {{ 'authentication.credentials_expired.message'|trans|desc( 'For security reasons, your password has expired and needs to be changed. An email has been sent to you with instructions.' ) }}

    {%- endblock -%} ``` ## Customize login form You can use a custom template for example to display information about password expiration or to customize [other user management templates](https://doc.ibexa.co/en/latest/users/user_registration/#other-user-management-templates). In case of more advanced template customization, you can use a subscriber, for example in `src/EventSubscriber/LoginFormViewSubscriber.php`: ``` 'onPreContentView', ]; } public function onPreContentView(PreContentViewEvent $event): void { $view = $event->getContentView(); if (!($view instanceof LoginFormView)) { return ; } $view->addParameters([ 'foo' => 'foo', 'bar' => 'bar' ]); if ($view->getLastAuthenticationException() instanceof CredentialsExpiredException) { // View with instruction to unlock account $view->setTemplateIdentifier('login/expired_credentials.html.twig'); } } } ``` In the provided example, in line 23, the `PRE_CONTENT_VIEW` event is used. You can also pass additional parameters to the view (line 35). In this case, at the instance of exception (line 40), the subscriber displays the `expired_credentials.html.twig` template (line 42). Remember to provide a template and point to it in the subscriber (in this case, in `templates/login/expired_credentials.html.twig`): ``` {% extends '@ibexadesign/Security/base.html.twig' %} {%- block content -%}

    {{ 'authentication.credentials_expired.message'|trans|desc( 'For security reasons, your password has expired and needs to be changed. An email has been sent to you with instructions.' ) }}

    {%- endblock -%} ``` For more information, see [Templates documentation](https://doc.ibexa.co/en/latest/templating/templates/templates/index.md). # Add navigation menu To add a navigation menu to your website, prepare a general layout template in a `templates/themes//pagelayout.html.twig` file. This template can contain things such as header, menu, footer, and [assets](https://doc.ibexa.co/en/latest/templating/assets/index.md) for the whole site, and all other templates [extend](https://doc.ibexa.co/en/latest/templating/templates/templates/#connecting-templates) it. To select items that should be rendered in the menu, you can use one of the following ways: - create a [query](#render-menu-using-a-query) - create a [MenuBuilder](#create-a-menubuilder) ## Render menu using a query To create a menu that contains a specific set of content items, for example all content under the root location, use a [Query Type](https://doc.ibexa.co/en/latest/templating/queries_and_controllers/content_queries/index.md). First, in `src/QueryType`, create a custom `MenuQueryType.php` file that queries for all items that you want in the menu: ``` $criteria, 'sortClauses' => [ new SortClause\Location\Priority(LocationQuery::SORT_ASC), ], ]; return new LocationQuery($options); } public static function getName() { return 'Menu'; } public function getSupportedParameters() { return []; } } ``` In this case, it queries for all visible children of location `2`, the root location, (lines 15-16) and renders them in order according to their location priority. The Query Type has the name `Menu` (line 28). You can use it in the template to render the menu. Add the following [`ibexa_render_content_query` function](https://doc.ibexa.co/en/latest/templating/twig_function_reference/content_twig_functions/#ibexa_render_content_query) to the `pagelayout_html.twig` template: ``` {{ ibexa_render_content_query({ 'query': { 'query_type': 'Menu', 'assign_results_to': 'menuItems' }, 'template': '@ibexadesign/pagelayout_menu.html.twig', }) }} ``` Next, add the `templates/themes//pagelayout_menu.html.twig` template, which renders the individual items of the menu: ``` {% if menuItems is defined and menuItems is not empty %} {% for item in menuItems %}
  • {{ ibexa_content_name(item.valueObject.contentInfo) }}
  • {% endfor %} {% endif %} ``` ## Create a MenuBuilder To make a more configurable menu, where you select the specific items to render, use the [KNPMenuBundle](https://github.com/KnpLabs/KnpMenuBundle) that is installed together with the product. To use it, first create a `MenuBuilder.php` file in `src/Menu`: ``` factory->createItem('root'); $menu->addChild('Home', ['route' => 'ibexa.url.alias', 'routeParameters' => [ 'locationId' => 2, ]]); $menu->addChild('Blog', ['route' => 'ibexa.url.alias', 'routeParameters' => [ 'locationId' => 67, ]]); $menu->addChild('Search', ['route' => 'ibexa.search']); return $menu; } } ``` In the builder, you can define items that you want in the menu. For example, lines 21-23 add a specific location by using the [`ibexa.url.alias`](https://doc.ibexa.co/en/latest/templating/twig_function_reference/url_twig_functions/#ibexaurlalias) route. Line 27 adds a defined system route that leads to the search form. Next, register the menu builder as a service: ``` services: App\Menu\MenuBuilder: tags: - {name: knp_menu.menu_builder, method: buildMenu, alias: root} ``` Finally, you can render the menu in `pagelayout.html.twig`. Identify it by the name that you provided in the Menu Builder's `buildMenu()` method: ``` {{ knp_menu_render('root') }} ``` # Add search form to front page You can add a search form to selected parts of your front page and decide which parts of the form, such as filters, are rendered. This example shows how to add a basic search bar to the top of every page and to configure search form and result rendering. ## Add a search bar First, prepare a general layout template in a `templates/themes//pagelayout.html.twig` file, and include a search bar in this template: ``` {% include '@ibexadesign/parts/search_bar.html.twig' %} {% block content %} {% endblock %} ``` Then, make sure that `pagelayout.html.twig` is included in your view configuration: ``` ibexa: system: site_group: page_layout: '@ibexadesign/pagelayout.html.twig' search_view: ``` The `parts/search_bar.html.twig` template uses the built-in `SearchController` to manage the search: ``` ``` You can now go to the front page of your installation. An unstyled search bar appears at the top of the page. ## Customize search result page Search results are shown in the `/search` route. You can go directly to `/search` to view a full search page. Select the template that is used on this page with the following configuration: ``` ibexa: system: site_group: page_layout: '@ibexadesign/pagelayout.html.twig' search_view: full: default: template: "@ibexadesign/full/search.html.twig" match: [ ] ``` Now, add the `full/search.html.twig` template: ``` {% block content %}
    {% include '@ibexadesign/parts/search_form.html.twig' with { form: form } %} {% if results is defined %}
    {{ 'search.header'|trans({'%total%': pager.nbResults})|desc('%total% search result(s):') }}
    {% if results is empty %}
    {{ 'search.no_result'|trans({'%query%': form.vars.value.query})|desc('No results found for "%query%".') }}
    {% else %}

    {{ 'search.name'|trans|desc('Name') }}

    {% if pager.haveToPaginate %}
    {{ pagerfanta(pager, '', {'pageParameter': '[search][page]'}) }}
    {% endif %} {% endif %} {% endif %}
    {% endblock %} ``` This template replaces the default table that displays search results with an unnumbered list. ## Render search form In the template above, line 5 includes a separate template for the search form. Create the `parts/search_form.html.twig` file: ``` {{ form_start(form) }}
    {{ form_row(form.query) }}
    {{ form_end(form, {'render_rest': false}) }} ``` This template renders only a basic query field and a submit button. `'render_rest': false` ensures that the fields you don't explicitly add to the template aren't rendered (in this case, date selection, content type, and more). # AI Actions # AI Actions AI Actions enhance the usability and flexibility of Ibexa DXP by automating various tasks. After you configure it, it can generate alt text for images or transform text passages. You can also extend it to perform other tasks or support additional AI services. ## Getting Started - [AI Actions product guide](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/ai_actions/ai_actions_guide/): AI Actions help editors by automating repetitive tasks. - [Configure AI Actions](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/ai_actions/configure_ai_actions/): Configure AI Actions. - [Taxonomy suggestions](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/content_management/taxonomy/taxonomy/#taxonomy-suggestions): Learn how to use AI to suggest tags and categories - [Policies](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/permissions/policies/#ai-actions): Learn about the available AI Actions policies - [Work with AI Actions](https://doc.ibexa.co/projects/userguide/en/5.0/ai_actions/work_with_ai_actions/): Create new AI actions or modify existing ones to work faster and increase creativity. ## Development - [Extend AI Actions](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/ai_actions/extend_ai_actions/): Extend AI Actions by connecting to other services and adding new capabilities. - [AI Actions events](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/api/event_reference/ai_action_events/): Events that are triggered when working with AI actions. - [REST API Reference](https://doc.ibexa.co/en/5.0/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Connector-AI): See the available endpoints for AI Actions - [AI Actions Twig functions](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/templating/twig_function_reference/ai_actions_twig_functions/): AI Actions functions allows you to embed AI Actions in your templates - [Action Configuration Search Criterion reference](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/search/ai_actions_search_reference/action_configuration_criteria/): Search Criteria available for Action Configuration search - [Action Configuration Search Sort Clauses reference](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/search/ai_actions_search_reference/action_configuration_sort_clauses/): Sort Clauses available for Action Configuration search - [Importing AI actions](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/content_management/data_migration/importing_data/#ai-action-configurations): Learn how to manage Action Configurations using data migrations # AI Actions product guide ## What are AI Actions Wherever you look, artificial intelligence becomes more and more important by enhancing user interaction and automating complex processes. Ibexa DXP is equipped with the AI Actions feature, which harnesses AI's potential to automate time-consuming editorial tasks. AI Actions is an extensible solution for integrating features provided by AI services into your workflows, all managed through a user-friendly interface. Out-of-the-box, AI Actions solution includes two essential components: a framework package and an OpenAI connector package. The Anthropic and Gemini connectors are also available - as [LTS updates](https://doc.ibexa.co/en/latest/ibexa_products/editions/#lts-updates). AI Actions can integrate with [Ibexa Connect](https://doc.ibexa.co/projects/connect/en/latest/general/ibexa_connect/), to give you an opportunity to build complex data transformation workflows without having to rely on custom code. From the developer's perspective, the integration removes the burden of maintaining third-party AI handlers, and accelerates the deployment of AI-based solutions. AI Actions solution comes pre-configured with the following action types: - [Refine text](#refining-text): Rewrite existing text according to instructions set in a prompt - [Generate alternative text](#generating-alternative-text): Generate alt text for images for accessibility purposes - [Suggest taxonomy entries](#suggesting-taxonomy-entries): Generate tag or product category suggestions based on content fields *[Image: AI Actions schematic]* You can extend the solution's capabilities beyond the default setup by creating custom connector modules, allowing users to take advantage of additional AI services, or customize the way data is processed and interpreted. For example, it could transform images, or generate illustrations for your articles based on their contents. The possibilities are endless and you're not limited to a specific AI service, avoiding vendor lock-in. ## Availability Ibexa Cloud is available in all Ibexa DXP editions. To begin using AI Actions, you must first [perform the initial configuration](https://doc.ibexa.co/en/latest/ai_actions/configure_ai_actions/index.md). ### Prerequisites Connectors with external AI services delivered by Ibexa require that you first install them, and [configure other settings, such as an API key and billing method](https://doc.ibexa.co/en/latest/ai_actions/configure_ai_actions/index.md). Integration with Ibexa Connect requires that you first [get the credentials](https://doc.ibexa.co/projects/connect/en/latest/general/ibexa_connect/#access-ibexa-connect) to your account, and the [API token](https://doc.ibexa.co/en/latest/ai_actions/configure_ai_actions/#create-token). > **Note: Ibexa Connect Availability** > > Ibexa Connect comes with all contracts signed from 2023. If you signed your contract earlier, contact your customer success manager to use Ibexa Connect. ## How it works AI Actions rely on an extensible AI framework, which is responsible for gathering information from various sources, such as AI action types, AI action configurations, and contextual details like SiteAccess, user details, locale settings, and more. This data can then be combined with user input. It's then passed to a service connector, such as the default OpenAI connector or the Ibexa Connect connector, for final processing on Ibexa DXP side. The service connector wraps all data into a prompt or another suitable format and sends it to an external service. When the external service returns a response, the response goes back through the service connector and passes to the framework. It can then be presented to the user in any way necessary. ### Core concepts #### AI service AI service is a third party platform that provides access to artificial intelligence tools and capabilities. It executes tasks that it receives through a service connector. #### Action Actions are tasks or functions that are executed by an external AI service. Each action is a combination of an AI action type and an AI action configuration. Action types define what kind of task the AI service performs, while AI action configurations specify how the task should be executed. This clear separation allows for a flexible system where actions can be created, managed, and customized with minimal effort. #### AI action type AI action types are high level templates predefined by developers. AI action types correspond to tasks that users intend to perform when they interact with the interface. Each AI action type defines the structure and nature of the task that the AI service performs, and is interpreted by a handler. Action type definitions specify the following information: - an identifier - a set of input parameters - a set of output fields - a category of action, for example, "text to image", "video to text" AI action types could be designed, for example, to generate alternative text based on an image, translate a selected passage of text, or generate a video clip based on a description provided in the field. By defining AI action types, developers can create a wide range of functionalities that can be deployed within the application. #### AI action configuration AI action configurations store detailed parameters needed to generate AI actions based on AI action types. Website administrators manage AI action configurations in the [**Admin** panel](https://doc.ibexa.co/en/latest/administration/admin_panel/admin_panel/index.md), where they customize and fine-tune the behavior of each AI action. It might involve setting specific parameters used by the AI service, a response length, an expense limit, or configuring how the output should be handled. By making such adjustments, administrators can ensure that the actions are tailored to meet the needs of your organization. #### Model Once an AI action is defined and configured, it must be executed, and this is where models come into play. Each model is designed to work with a specific AI service and AI action type pair. Pieces of PHP code that are responsible for resolving a model are called handlers. They may include hardcoded prompts for conversational AI services like ChatGPT, or operate without prompts in the case of other types of AI. Handlers take parameters defined in the AI action type and configuration, combine it with user input and any predefined settings or prompts, and pass this information to the AI service for processing. ### Triggering actions from the UI Among other elements, AI Actions include UI components that are used in: - AI action management in the **Admin** panel - text modification in online editor - alt-text generation in the image management modal These areas are user-friendly and well integrated with the existing application’s UI. Administrators can manage action configurations with ease, while editors can trigger actions with a click of a button. Procedures are straightforward and intuitive, ensuring that users can quickly achieve their desired outcomes. ### Triggering actions programmatically AI Actions feature exposes a REST API interface that allows for programmatic execution of AI actions. With the API, developers can automate tasks and execute actions on batches of content by integrating them into workflows. For more information, see the [AI actions section in the REST API Reference](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#ai-actions-execute-ai-action). ## Capabilities ### Management Users with the appropriate permissions, governed by role-based [policies](https://doc.ibexa.co/en/latest/permissions/policies/#ai-actions), can control the lifecycle of AI actions by creating, editing, executing, and deleting them. Additionally, AI action configurations can be enabled or disabled depending on the organization's needs. *[Image: Configurations management screen]* An intuitive AI Actions interface within the **Admin** panel displays a list of all available AI actions. Here, you can search for specific actions and filter them by type or status. By accessing the detailed view of individual AI actions, you can quickly review all their parameters. ### Extensibility Built-in AI action types offer a good starting point, but the real power of AI Actions lies in extensibility. Extending AI Actions opens up new possibilities for content management and editing. Developers can define new models and AI action types that use the existing AI service or even integrate additional services. The latter involves developing a new service connector, writing a handler that communicates with the new service, defining a new AI action type, and creating a form for configuring options, which extends the default action configuration form shown in the **Admin** panel. For example, if this is your organization's requirement, a developer could write a handler that uses an AI service available internally, without exposing your data to a third-party service. ## Use cases Out of the box, after you configure access to the OpenAI service, the Ibexa AI Actions come with two action types that can help your organization with the following tasks. ### Refining text Content editors can benefit from using AI capabilities to [enhance or modify text](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/create_edit_content_items/#ai-assistant). With a few clicks, they can improve content quality or reduce the workload. While working on content, editors can request that AI performs specific actions such as: adjusting the length of the text, changing the tone, or correcting linguistic errors. *[Image: AI Assistant]* This functionality is available in content types that include RichText, Text line, Text Block fields, and certain Page Builder blocks. ### Generating alternative text Media managers and content editors can benefit from employing AI to [generate alt text for images](https://doc.ibexa.co/projects/userguide/en/5.0/image_management/upload_images/#ai), which results in improved accessibility and SEO. Once the feature is configured, editors can generate alt text for images they upload to the system by clicking one button. *[Image: Alt text generation]* With some customization, administrators could use the API to run a batch process against a larger collection of illustrations. ### Suggesting taxonomy entries Content editors and product managers can use [taxonomy suggestions](https://doc.ibexa.co/en/latest/content_management/taxonomy/taxonomy/#taxonomy-suggestions) when assigning tags or product categories to content items and products. Instead of manually browsing through extensive taxonomy trees, editors can request suggestions based on the content's text fields, such as name and description. > **Note: Alternative suggestion provider** > > By default, embeddings used by the taxonomy suggestions feature are generated with OpenAI. If you install and configure the [Google Gemini connector](https://doc.ibexa.co/en/latest/ai_actions/configure_ai_actions/#install-google-gemini-connector), you can modify the [taxonomy suggestions settings](https://doc.ibexa.co/en/latest/content_management/taxonomy/taxonomy/#change-embeddings-provider-to-google-gemini) and use Google Gemini as an alternative embeddings provider. ### Performing advanced image to text analysis With some additional customization, store managers could benefit from automating part of product management by integrating their Ibexa DXP with Google Cloud Vision and the [product catalog](https://doc.ibexa.co/en/latest/product_catalog/product_catalog_guide/index.md) by using Ibexa Connect. Instead of manually selecting and linking images stored in a [DAM](https://doc.ibexa.co/en/latest/content_management/images/add_image_asset_from_dam/index.md) solution to their products, they could use of a no-code workflow where an AI service, for example, Google Cloud Vision, extracts text and attributes from product images, which are then matched with existing items in a product catalog. This would enable automatic product identification, tagging, and catalog updates, resulting in less manual work and more efficient product management. # Configure AI Actions AI Actions are available in Ibexa DXP regardless of its edition. To use this feature you must first configure the built-in service connectors or build your own ones. Once the framework is configured, before you can start using AI Actions, you can configure access to Ibexa-made service connectors by following the instructions below, or [create your own](https://doc.ibexa.co/en/latest/ai_actions/extend_ai_actions/#create-custom-action-handler). Only then you can restart you application and start [working with the AI Actions feature](https://doc.ibexa.co/projects/userguide/en/5.0/ai_actions/work_with_ai_actions/). > **Note: Taxonomy suggestions** > > The default OpenAI or the optional Google Gemini connectors can used by the [Taxonomy suggestions](https://doc.ibexa.co/en/latest/content_management/taxonomy/taxonomy/#taxonomy-suggestions) feature to generate embeddings for suggesting tags and product categories. After you configure the OpenAI connector, or set up the optional Google Gemini connector and [modify the default taxonomy suggestions settings](https://doc.ibexa.co/en/latest/content_management/taxonomy/taxonomy/#change-embeddings-provider-to-google-gemini), you can [create AI actions that use the Text to Taxonomy action type](https://doc.ibexa.co/projects/userguide/en/5.0/ai_actions/work_with_ai_actions/#create-ai-actions-that-control-taxonomy-suggestions). You can also create [your own embedding provider](https://doc.ibexa.co/en/latest/content_management/taxonomy/taxonomy/#replace-the-embedding-provider). ## Configure access to OpenAI To use the built-in connector with the OpenAI service, you need to create an OpenAI account, [get an API key](https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key), and make sure that you [set up a billing method](https://help.openai.com/en/articles/9038407-how-can-i-set-up-billing-for-my-account). Then, in the root folder of your project, modify the `.env` file: find the `OPENAI_API_KEY` variable and replace a placeholder value with the API key that you got from the AI service. ``` ###> ibexa/connector-openai ### OPENAI_API_KEY= ###< ibexa/connector-openai ### ``` ### Sample OpenAI action configurations The AI actions come with sample AI action configurations to quickly get you started on using the feature. Based on these examples, which reflect the most common use cases, you can learn to configure your own AI actions with greater ease. ## Install Anthropic connector (LTS Update) Run the following command to install the package: ``` composer require ibexa/connector-anthropic ``` This command adds the feature code, including basic handlers that let you refine text or generate alternative text for images. To use the connector with the Anthropic services, you need to create an account, make sure that you [set up a billing method](https://support.claude.com/en/articles/8325618-paid-plan-billing-faqs), and get an API key. 1. Log in to your [Anthropic Claude console](https://console.anthropic.com/login). 2. Go to **API keys** and click **Create Key**. 3. Select the workspace, enter a **Key Name** and click **Add**. 4. Take a note of the API key, because it is displayed only once. Then, in the root folder of your project, modify the `.env` file: add an `ANTHROPIC_API_KEY` variable and populate its value with the API key that you got from the AI service. ``` ###> ibexa/connector-anthropic ### ANTHROPIC_API_KEY= ###< ibexa/connector-anthropic ### ``` By default, when reaching out for responses, the Anthropic connector uses the [Claude Sonnet 4](https://docs.claude.com/en/docs/about-claude/models/overview) model. Users can override this setting at runtime when they [edit or create an AI action](https://doc.ibexa.co/projects/userguide/en/5.0/ai_actions/work_with_ai_actions/#edit-existing-ai-actions). You can also change the default values globally. To do it, in `config/packages` folder, create a YAML file similar to this example: ``` ibexa_connector_anthropic: text_to_text: default_model: claude-opus-4-20250514 default_temperature: 0.8 default_max_tokens: 2045 models: claude-sonnet-4-20250514: 'Claude 4 Sonnet (2025-05-14)' claude-opus-4-20250514: 'Claude Opus 4 (2025-05-14)' ``` You can now use the Anthropic connector in your project. ## Install Google Gemini connector (LTS Update) Run the following command to install the package: ``` composer require ibexa/connector-gemini ``` This command adds the feature code, including basic handlers that let you refine text or generate alternative text for images. ### Get API key To use the connector with the Gemini services, you need to create an account, set up billing, enable Gemini API and get an API key. #### Create the Google Cloud project 1. Sign in to the [Google Cloud Console](https://console.cloud.google.com/). 2. In the top bar, click **Default Gemini Project** to open a project picker. 3. Click **New project** and provide project details: 1. Add project name, for example, "My project". 2. Modify the automatically generated **Project ID** if necessary. 3. Select location: choose your organization. 4. Click **Create**. #### Configure billing 1. Navigate to the Google Cloud Console's **Billing** page. 2. If you do not have one, click **Add billing account** and add a payment method. 3. In **Your projects** tab, locate your project, and in its line, from the **Actions** menu, select **Change billing**. 4. Select your active billing account, and click **Set account**. #### Enable the Gemini API 1. Navigate to the Google Cloud Console's **APIs & Services** page. 2. From the left-hand menu, select **Library** and search for the Generative Language API. 3. In the API's details page, click **Enable**. #### Generate the API key 1. Go to [Google AI Studio](https://aistudio.google.com/app/api-keys)'s **API keys** page, and click **Create API key**. 2. Provide a name for the API key, select "My project" from a list of projects and click **Create key**. 3. Back in the **API keys** list, in your project's line, copy the API key. ### Set API key in configuration Then, in the root folder of your project, modify the `.env` file: add an `GEMINI_API_KEY` variable and populate its value with the API key that you got from the AI service. ``` ###> ibexa/connector-gemini ### GEMINI_API_KEY= ###< ibexa/connector-gemini ### ``` > **Note: Different API keys for different SiteAccesses** > > If there are multiple SiteAccesses in your installation, you can set different API keys for each SiteAccess. To do it, set the keys under the `ibexa.system.` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files), like so: > > ``` > ibexa: > system: > default: > connector_gemini: > gemini: > api_key: '%env(GEMINI_API_KEY)%' > base_url: 'https://generativelanguage.googleapis.com/v1beta/' # Google Gemini's API endpoint > ``` ### Configure default models By default, when reaching out for responses, the Gemini connector uses the Gemini Pro [model](https://ai.google.dev/gemini-api/docs/models) for text refinement and Gemini Flash model for alternative text generation. Users can override this setting at runtime when they [edit or create an AI action](https://doc.ibexa.co/projects/userguide/en/5.0/ai_actions/work_with_ai_actions/#edit-existing-ai-actions). You can also change the default values globally. To do it, in `config/packages` folder, create a YAML file similar to this example: ``` ibexa_connector_gemini: text_to_text: models: gemini-pro-latest: label: 'Gemini Pro Latest' max_tokens: 4096 gemini-flash-latest: label: 'Gemini Flash Latest' max_tokens: 4096 default_model: gemini-pro-latest default_max_tokens: 4096 # Must be <= the model’s max_tokens default_temperature: 0.8 image_to_text: models: gemini-flash-latest: label: 'Gemini Flash Latest' max_tokens: 4096 default_model: gemini-flash-latest default_max_tokens: 4096 default_temperature: 1.0 ``` When setting up models, make sure that you follow these rules: - `default_model` must reference a configured model - `default_max_tokens` must not exceed the model’s limit - If you use the same model for different action types, settings must be consistent > **Note: Google Gemini and taxonomy suggestions** > > To use Google Gemini for generating taxonomy suggestions, ensure that you [change the embeddings provider and model setting accordingly](https://doc.ibexa.co/en/latest/content_management/taxonomy/taxonomy/#change-embeddings-provider-to-google-gemini). You can now use the Gemini connector in your project. For more information, see [Extend Gemini connector](https://doc.ibexa.co/en/latest/ai_actions/extend_ai_actions/#extend-google-gemini-connector). ## Configure access to Ibexa Connect First, get the credentials by contacting [Ibexa Support](https://support.ibexa.co). ### Create team In Ibexa Connect, set up the account, and [create a team](https://doc.ibexa.co/projects/connect/en/latest/access_management/teams/#creating-teams). Navigate to the team details page and note down the numerical value of the **Team id** variable. Creating a team matters, because [scenarios](https://doc.ibexa.co/projects/connect/en/latest/scenarios/creating_a_scenario/) that process data coming from your AI action are associated with a team. This way, if your organization has more than one Ibexa DXP project, each project can be linked to a different team and so can be scenarios used in those projects. If specific users from the team are supposed to modify scenario settings, you must [assign the right roles](https://doc.ibexa.co/projects/connect/en/latest/access_management/teams/#managing-teams) to them. ### Create token Navigate to your Ibexa Connect user's profile, and on the **API ACCESS** tab, create a new token. Select the following scopes to set permissions needed to enable the integration of platforms: - `custom-property-structures:read` - `custom-property-structures:write` - `hooks:read` - `hooks:write` - `scenarios:read` - `scenarios:write` - `team-variables:read` - `team-variables:write` - `teams:write` - `templates:read` - `templates:write` - `udts:read` - `udts:write` *[Image: Creating an API token]* Copy the token code that appears on the tokens list, next to the label. ### Set up credentials In the root folder of your project, modify the `.env` file. Replace a placeholder value of the `IBEXA_CONNECT_TOKEN` variable with the token that you got from Ibexa Connect and provide a value of the `IBEXA_CONNECT_TEAM_ID` variable. ``` ###> ibexa/connect ### IBEXA_CONNECT_HOST=https://connect.ibexa.co IBEXA_CONNECT_API_PATH=/api/v2/ # Token can be created in the user's profile in Ibexa Connect, under the 'API ACCESS' section. IBEXA_CONNECT_TOKEN= # Use the URL below to read more on Ibexa Connect teams. # https://doc.ibexa.co/projects/connect/en/latest/access_management/teams/ IBEXA_CONNECT_TEAM_ID=2 ###< ibexa/connect ### ``` ### Initiate integration Initiate the models provided by the handler by issuing the following command: ``` php bin/console ibexa:connect:init-connect-ai ``` For example: ``` php bin/console ibexa:connect:init-connect-ai 2 en connect-image-to-text connect-text-to-text ``` > **Note: Support for multiple Ibexa Connect languages** > > The [`language` attribute](https://developers.make.com/api-documentation/api-reference/templates#post-templates) determines the language in which template details such as module names will be displayed in Ibexa Connect's UI. Then, create the `Ibexa AI handler` custom property in Ibexa Connect to store the list of available action handlers for this integration. You can do it by running the following command: ``` php bin/console ibexa:connect:init-custom-property-structures ``` For example: ``` php bin/console ibexa:connect:init-custom-property-structures 4 connect-image-to-text connect-text-to-text ``` The `Ibexa AI handler` property attaches to a scenario to store information about the action handler associated with it. When creating a new Ibexa Connect-based AI action, the back office of Ibexa DXP shows only the existing scenarios that work with selected action handler. ### Customize templates Return to the Ibexa Connect dashboard and modify the **Template for connect...handler** [templates](https://doc.ibexa.co/projects/connect/en/latest/scenarios/scenario_templates/) by defining the logic needed to process the data. Once the templates are ready, you can build scenarios from them, either directly in Ibexa Connect or in [Ibexa DXP's user interface](https://doc.ibexa.co/projects/userguide/en/5.0/ai_actions/work_with_ai_actions/#create-new-ai-actions). # Extend AI Actions By extending [AI Actions](https://doc.ibexa.co/en/latest/ai_actions/ai_actions_guide/index.md), you can make regular content management and editing tasks more appealing and less demanding. You can start by integrating additional AI services to the existing action types or develop custom ones that impact completely new areas of application. For example, you can create a handler that connects to a translation model and use it to translate your website on-the-fly, or generate illustrations based on a body of an article. ## Execute Actions You can execute AI Actions by using the [ActionServiceInterface](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionServiceInterface.html) service, as in the following example: ``` $action = new GenerateAltTextAction(new Image([$imageEncodedInBase64])); $action->setRuntimeContext(new RuntimeContext(['languageCode' => $languageCode])); $action->setActionContext( new ActionContext( new ActionConfigurationOptions(['default_locale_fallback' => 'en']), // System context new ActionConfigurationOptions(['max_lenght' => 100]), // Action Type options new ActionConfigurationOptions( // Action Handler options [ 'prompt' => 'Generate the alt text for this image in less than 100 characters.', 'temperature' => 0.7, 'max_tokens' => 4096, 'model' => 'gpt-4o-mini', ] ) ) ); $output = $this->actionService->execute($action)->getOutput(); ``` The `GenerateAltTextAction` is a built-in action that implements the [ActionInterface](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionInterface.html), takes an [Image](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-DataType-Image.html) as an input, and generates the alternative text in the response. This action is parameterized with the [RuntimeContext](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-RuntimeContext.html) and the [ActionContext](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-ActionContext.html), which allows you to pass additional options to the Action before it's executed. | Type of context | Type of options | Usage | Example | | --------------- | ---------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | | Runtime Context | Runtime options | Sets additional parameters that are relevant to the specific action that is currently executed | Information about the language of the content that is being processed | | Action Context | Action Type options | Sets additional parameters for the Action Type | Information about the expected response length | | Action Context | Action Handler options | Sets additional parameters for the Action Handler | Information about the model, temperature, prompt, and max tokens allowed | | Action Context | System options | Sets additional information, not matching the other option collections | Information about the fallback locale | Both `ActionContext` and `RuntimeContext` are passed to the Action Handler (an object implementing the [ActionHandlerInterface](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-ActionHandlerInterface.html)) to execute the action. The Action Handler is responsible for combining all the options together, sending them to the AI service and returning an [ActionResponse](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionResponseInterface.html). You can pass the Action Handler directly to the `ActionServiceInterface::execute()` method, which overrides all the other ways of selecting the Action Handler. You can also specify the Action Handler by including it in the provided [Action Configuration](#action-configurations). In other cases, the Action Handler is selected automatically. You can affect this choice by creating your own class implementing the [ActionHandlerResolverInterface](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-ActionHandlerResolverInterface.html) or by listening to the [ResolveActionHandlerEvent](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Events-ResolveActionHandlerEvent.html) Event sent by the default implementation. You can influence the execution of an Action with two events: - [BeforeExecuteEvent](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-Event-BeforeExecuteEvent.html), fired before the Action is executed - [ExecuteEvent](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-Event-ExecuteEvent.html), fired after the Action is executed Below you can find the full example of a Symfony Command, together with a matching service definition. The command finds the images modified in the last 24 hours, and adds the alternative text to them if it's missing. ``` addArgument('user', InputArgument::OPTIONAL, 'Login of the user executing the actions', 'admin'); } protected function execute(InputInterface $input, OutputInterface $output): int { $this->setUser($input->getArgument('user')); $modifiedImages = $this->getModifiedImages(); $output->writeln(sprintf('Found %d modified image in the last 24h', $modifiedImages->getTotalCount())); /** @var \Ibexa\Contracts\Core\Repository\Values\Content\Content $content */ foreach ($modifiedImages as $content) { /** @var \Ibexa\Core\FieldType\Image\Value $value */ $value = $content->getFieldValue(self::IMAGE_FIELD_IDENTIFIER); if ($value === null || !$this->shouldGenerateAltText($value)) { $output->writeln(sprintf('Image %s has the image field empty, the file cannot be accessed, or the alternative text is already specified. Skipping.', $content->getName())); continue; } $contentUpdateStruct = $this->contentService->newContentUpdateStruct(); $value->alternativeText = $this->getSuggestedAltText($this->convertImageToBase64($value->uri), $content->getDefaultLanguageCode()); $contentUpdateStruct->setField(self::IMAGE_FIELD_IDENTIFIER, $value); $updatedContent = $this->contentService->updateContent( $this->contentService->createContentDraft($content->getContentInfo())->getVersionInfo(), $contentUpdateStruct ); $this->contentService->publishVersion($updatedContent->getVersionInfo()); } return Command::SUCCESS; } private function getSuggestedAltText(string $imageEncodedInBase64, string $languageCode): string { $action = new GenerateAltTextAction(new Image([$imageEncodedInBase64])); $action->setRuntimeContext(new RuntimeContext(['languageCode' => $languageCode])); $action->setActionContext( new ActionContext( new ActionConfigurationOptions(['default_locale_fallback' => 'en']), // System context new ActionConfigurationOptions(['max_lenght' => 100]), // Action Type options new ActionConfigurationOptions( // Action Handler options [ 'prompt' => 'Generate the alt text for this image in less than 100 characters.', 'temperature' => 0.7, 'max_tokens' => 4096, 'model' => 'gpt-4o-mini', ] ) ) ); $output = $this->actionService->execute($action)->getOutput(); assert($output instanceof Text); return $output->getText(); } private function convertImageToBase64(string $uri): string { $id = $this->binaryDataHandler->getIdFromUri($uri); $file = $this->binaryDataHandler->getContents($id); return 'data:image/jpeg;base64,' . base64_encode($file); } private function getModifiedImages(): ContentList { $filter = (new Filter()) ->withCriterion( new DateMetadata(DateMetadata::MODIFIED, Operator::GTE, strtotime('-1 day')) ) ->andWithCriterion(new ContentTypeIdentifier('image')); return $this->contentService->find($filter); } /** @phpstan-assert-if-true string $value->uri */ private function shouldGenerateAltText(Value $value): bool { return $this->fieldTypeService->getFieldType('ibexa_image')->isEmptyValue($value) === false && $value->isAlternativeTextEmpty() && $value->uri !== null; } private function setUser(string $userLogin): void { $this->permissionResolver->setCurrentUserReference($this->userService->loadUserByLogin($userLogin)); } } ``` ``` App\Command\AddMissingAltTextCommand: arguments: $binaryDataHandler: '@Ibexa\Core\IO\IOBinarydataHandler\SiteAccessDependentBinaryDataHandler' ``` Executing Actions this way has a major drawback: all the parameters are stored directly in the code and cannot be easily reused or changed. To manage configurations of an AI Action you need to use another concept: Action Configurations. ## Action Configurations ### Manage Action Configurations Action Configurations allow you to store the parameters for a given Action in the database and reuse them when needed. They can be managed [through the back office](https://doc.ibexa.co/projects/userguide/en/5.0/ai_actions/work_with_ai_actions/), [data migrations](https://doc.ibexa.co/en/latest/content_management/data_migration/importing_data/#ai-action-configurations), or through the PHP API. To manage Action Configurations through the PHP API, you need to use the [ActionConfigurationServiceInterface](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfigurationServiceInterface.html) service. You can manage them using the following methods: - Creating them with `ActionConfigurationServiceInterface::createActionConfiguration()` by passing the [ActionConfigurationCreateStruct](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-ActionConfigurationCreateStruct.html). - Updating them with `ActionConfigurationServiceInterface::updateActionConfiguration()` by passing the [ActionConfigurationUpdateStruct](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-ActionConfigurationUpdateStruct.html). - Deleting them with `ActionConfigurationServiceInterface::deleteActionConfiguration()` by passing the [ActionConfigurationInterface](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfigurationInterface.html). See the [AI Actions event reference](https://doc.ibexa.co/en/latest/api/event_reference/ai_action_events/#action-configurations-management) for a list of events related to these operations. You can get a specific Action Configuration using the `ActionConfigurationServiceInterface::getActionConfiguration()` method and search for them using the `ActionConfigurationServiceInterface::findActionConfigurations()` method. See [Action Configuration Search Criteria reference](https://doc.ibexa.co/en/latest/search/ai_actions_search_reference/action_configuration_criteria/index.md) and [Action Configuration Search Sort Clauses reference](https://doc.ibexa.co/en/latest/search/ai_actions_search_reference/action_configuration_sort_clauses/index.md) to discover query possibilities. The following example creates a new Action Configuration: ``` $refineTextActionType = $this->actionTypeRegistry->getActionType('refine_text'); $actionConfigurationCreateStruct = new ActionConfigurationCreateStruct('rewrite_casual'); $actionConfigurationCreateStruct->setType($refineTextActionType); $actionConfigurationCreateStruct->setName('eng-GB', 'Rewrite in casual tone'); $actionConfigurationCreateStruct->setDescription('eng-GB', 'Rewrites the text using a casual tone'); $actionConfigurationCreateStruct->setActionHandler('openai-text-to-text'); $actionConfigurationCreateStruct->setActionHandlerOptions(new ArrayMap([ 'max_tokens' => 4000, 'temperature' => 1, 'prompt' => 'Rewrite this content to improve readability. Preserve meaning and crucial information but use casual language accessible to a broader audience.', 'model' => 'gpt-4-turbo', ])); $actionConfigurationCreateStruct->setEnabled(true); $this->actionConfigurationService->createActionConfiguration($actionConfigurationCreateStruct); ``` Actions Configurations are tied to a specific Action Type and are translatable. ### Execute Actions with Action Configurations Reuse existing Action Configurations to simplify the execution of AI Actions. You can pass one directly to the `ActionServiceInterface::execute()` method: ``` $action = new RefineTextAction(new Text([ <<actionConfigurationService->getActionConfiguration('rewrite_casual'); $actionResponse = $this->actionService->execute($action, $actionConfiguration)->getOutput(); ``` The passed Action Configuration is only taken into account if the Action Context was not passed to the Action directly using the [ActionInterface::setActionContext()](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionInterface.html#method_hasActionContext) method. The `ActionServiceInterface` service extracts the configuration options from the Action Configuration object and builds the Action Context object internally: - Action Type options are mapped to Action Type options in the Action Context - Action Handler options are mapped to Action Handler options in the Action Context - System Context options are modified using the [ContextEvent](https://doc.ibexa.co/en/latest/api/event_reference/ai_action_events/#others) event ## Create custom Action Handler Ibexa DXP comes with a built-in connector to OpenAI services, but you're not limited to it and can add support for additional AI services in your application. The following example adds a new Action Handler connecting to a local AI run using [the llamafile project](https://github.com/Mozilla-Ocho/llamafile) which you can use to execute Text-To-Text Actions, such as the built-in "Refine Text" Action. When creating an Action Handler for Ibexa Connect, add the new handler identifier to the [`Ibexa AI handler` custom property](https://doc.ibexa.co/en/latest/ai_actions/configure_ai_actions/#initiate-integration) in Ibexa Connect user interface. ### Register a custom Action Handler in the system. Create a class implementing the [ActionHandlerInterface](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-ActionHandlerInterface.html) and register it as a service: - The `ActionHandlerInterface::supports()` method decides whether the Action Handler is able to execute given Action. - The `ActionHandlerInterface::handle()` method is responsible for combining all the Action options together, sending them to the AI service and forming an Action Response. - The `ActionHandlerInterface::getIdentifier()` method returns the identifier of the Action Handler which you can use to refer to it in other places in the code. See the code sample below, together with a matching service definition: ``` getInput(); $text = $this->sanitizeInput($input->getText()); $systemMessage = $action->hasActionContext() ? $action->getActionContext()->getActionHandlerOptions()->get('system_prompt', '') : ''; $response = $this->client->request( 'POST', sprintf('%s/v1/chat/completions', $this->host), [ 'headers' => [ 'Authorization: Bearer no-key', ], 'json' => [ 'model' => 'LLaMA_CPP', 'messages' => [ (object)[ 'role' => 'system', 'content' => $systemMessage, ], (object)[ 'role' => 'user', 'content' => $text, ], ], 'temperature' => 0.7, ], ] ); $output = strip_tags((string) json_decode($response->getContent(), true)['choices'][0]['message']['content']); return new TextResponse(new Text([$output])); } public static function getIdentifier(): string { return self::IDENTIFIER; } private function sanitizeInput(string $text): string { return str_replace(["\n", "\r"], ' ', $text); } } ``` ``` App\AI\Handler\LLaVATextToTextActionHandler: tags: - { name: ibexa.ai.action.handler, priority: 0 } - { name: ibexa.ai.action.handler.text_to_text, priority: 0 } ``` The `ibexa.ai.action.handler` tag is used by the `ActionHandlerResolverInterface` to find all the Action Handlers in the system. The built-in Action Types use service tags to find Action Handlers capable of handling them and display in the back office UI: - Refine Text uses the `ibexa.ai.action.handler.text_to_text` service tag - Generate Alt Text uses the `ibexa.ai.action.handler.image_to_text` service tag ### Provide Form configuration Form configuration makes the Handler configurable by using the back office. The example handler uses the `system_prompt` option, which becomes part of the Action Configuration UI thanks to the following code: ``` add('system_prompt', TextareaType::class, [ 'required' => true, 'disabled' => $options['translation_mode'], 'label' => 'System message', ]); } public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'translation_domain' => 'app_ai', 'translation_mode' => false, ]); $resolver->setAllowedTypes('translation_mode', 'bool'); } } ``` ``` app.connector_ai.action_configuration.handler.llava_text_to_text.form_mapper.options: class: Ibexa\Bundle\ConnectorAi\Form\FormMapper\ActionConfiguration\ActionHandlerOptionsFormMapper arguments: $formType: 'App\Form\Type\TextToTextOptionsType' tags: - name: ibexa.connector_ai.action_configuration.form_mapper.options type: !php/const \App\AI\Handler\LLaVaTextToTextActionHandler::IDENTIFIER ``` The created Form Type adds the `system_prompt` field to the Form. Use the `Ibexa\Bundle\ConnectorAi\Form\FormMapper\ActionConfiguration\ActionHandlerOptionsFormMapper` class together with the `ibexa.connector_ai.action_configuration.form_mapper.options` service tag to make it part of the Action Handler options form. Pass the Action Handler identifier (`LLaVATextToText`) as the type when tagging the service. The Action Handler and Action Type options are rendered in the back office using the built-in Twig options formatter. *[Image: Custom Action Handler options rendered using the default Twig options formatter]* You can create your own formatting by creating a class implementing the [OptionsFormatterInterface](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-OptionsFormatterInterface.html) interface and aliasing it to `Ibexa\Contracts\ConnectorAi\ActionConfiguration\OptionsFormatterInterface`. The following service definition switches the options rendering to the other built-in options formatter, displaying the options as JSON. ``` Ibexa\Contracts\ConnectorAi\ActionConfiguration\OptionsFormatterInterface: alias: Ibexa\ConnectorAi\ActionConfiguration\JsonOptionsFormatter ``` ## Custom Action Type use case With custom Action Types you can create your own tasks for the AI services to perform. They can be integrated with the rest of the AI framework provided by Ibexa and incorporated into the back office. The following example shows how to implement a custom Action Type dedicated for transcribing audio with an example Handler using [the OpenAI's Whisper](https://github.com/openai/whisper) project. ### Create custom Action Type Start by creating your own Action Type, a class implementing the [ActionTypeInterface](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionType-ActionTypeInterface.html). The class needs to define following parameters of the Action Type: - name - identifier - input type identifier - output type identifier - Action object ``` $actionHandlers*/ public function __construct(private iterable $actionHandlers) { } public function getIdentifier(): string { return self::IDENTIFIER; } public function getName(): string { return 'Transcribe audio'; } public function getInputIdentifier(): string { return Audio::getIdentifier(); } public function getOutputIdentifier(): string { return Text::getIdentifier(); } public function getOptions(): array { return []; } public function createAction(DataType $input, array $parameters = []): ActionInterface { if (!$input instanceof Audio) { throw new InvalidArgumentException( 'audio', 'expected \App\AI\DataType\Audio type, ' . get_debug_type($input) . ' given.' ); } return new TranscribeAudioAction($input); } public function getActionHandlers(): iterable { return $this->actionHandlers; } } ``` ``` App\AI\ActionType\TranscribeAudioActionType: arguments: $actionHandlers: !tagged_iterator tag: app.connector_ai.action.handler.audio_to_text default_index_method: getIdentifier index_by: key tags: - { name: ibexa.ai.action.type, identifier: !php/const \App\AI\ActionType\TranscribeAudioActionType::IDENTIFIER } ``` The service definition introduces a custom `app.connector_ai.action.handler.audio_to_text` service tag to mark all the handlers capable of working with this Action Type. The `ibexa.ai.action.type` service tag registers the class in the service container as a new Action Type. If the Action Type is meant to be used mainly with prompt-based systems you can use the [LLMBaseActionTypeInterface](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-LLMBaseActionTypeInterface.html) interface as the base for your Action Type. It allows you to define a base prompt directly in the Action Type that can be common for all Action Configurations. Action Type names can be localized using the Translation component. See the built-in Action Types like Generate Alt Text or Refine Text for an example. ### Create custom Data classes The `TranscribeAudio` Action Type requires adding two data classes that exist in its definition: - an `Audio` class, implementing the [DataType interface](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-DataType.html), to store the input data for the Action ``` */ final class Audio implements DataType { /** * @param non-empty-array $base64 */ public function __construct(private array $base64) { } public function getBase64(): string { return reset($this->base64); } public function getList(): array { return $this->base64; } public static function getIdentifier(): string { return 'audio'; } } ``` - an `TranscribeAudioAction` class, implementing the [ActionInterface interface](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionInterface.html). Pass this object to the `ActionServiceInterface::execute()` method to execute the action. ``` audio; } public function getActionTypeIdentifier(): string { return 'transcribe_audio'; } } ``` ### Create custom Action Type options form Custom Form Type is needed if the Action Type requires additional options configurable in the UI. The following example adds a checkbox field that indicates to the Action Handler whether the transcription should include the timestamps. ``` add('include_timestamps', CheckboxType::class, [ 'required' => false, 'disabled' => $options['translation_mode'], 'label' => 'Include timestamps', ]); } public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'translation_domain' => 'app_ai', 'translation_mode' => false, ]); $resolver->setAllowedTypes('translation_mode', 'bool'); } } ``` ``` app.connector_ai.action_configuration.handler.transcribe_audio.form_mapper.options: class: Ibexa\Bundle\ConnectorAi\Form\FormMapper\ActionConfiguration\ActionTypeOptionsFormMapper arguments: $formType: 'App\Form\Type\TranscribeAudioOptionsType' tags: - name: ibexa.connector_ai.action_configuration.form_mapper.action_type_options type: !php/const \App\AI\ActionType\TranscribeAudioActionType::IDENTIFIER ``` The built-in `Ibexa\Bundle\ConnectorAi\Form\FormMapper\ActionConfiguration\ActionTypeOptionsFormMapper` renders the Form Type in the back office when editing the Action Configuration for a specific Action Type (indicated by the `type` attribute of the `ibexa.connector_ai.action_configuration.form_mapper.action_type_options` service tag). ### Create custom Action Handler An example Action Handler combines the input data and the Action Type options and passes them to the Whisper executable to form an Action Response. The language of the transcribed data is extracted from the Runtime Context for better results. The Action Type options provided in the Action Context dictate whether the timestamps will be removed before returning the result. ``` \d{2}:\d{2}\.\d{3}]\s*/'; public function supports(ActionInterface $action): bool { return $action->getActionTypeIdentifier() === TranscribeAudioActionType::IDENTIFIER; } public function handle(ActionInterface $action, array $context = []): ActionResponseInterface { /** @var \App\AI\DataType\Audio $input */ $input = $action->getInput(); $path = $this->saveInputToFile($input->getBase64()); $arguments = ['whisper']; $language = $action->getRuntimeContext()?->get('languageCode'); if ($language !== null) { $arguments[] = sprintf('--language=%s', substr((string) $language, 0, 2)); } $arguments[] = '--output_format=txt'; $arguments[] = $path; $process = new Process($arguments); $process->run(); if (!$process->isSuccessful()) { unlink($path); throw new ProcessFailedException($process); } $output = $process->getOutput(); $includeTimestamps = $action->getActionContext() ?->getActionTypeOptions() ->get('include_timestamps', false) ?? false; if (!$includeTimestamps) { $output = $this->removeTimestamps($output); } unlink($path); return new TextResponse(new Text([$output])); } public static function getIdentifier(): string { return 'whisper_audio_to_text'; } private function removeTimestamps(string $text): string { $lines = explode(PHP_EOL, $text); $processedLines = array_map(static fn (string $line): string => preg_replace(self::TIMESTAMP_FORMAT, '', (string) $line) ?? '', $lines); return implode(PHP_EOL, $processedLines); } private function saveInputToFile(string $audioEncodedInBase64): string { $filename = uniqid('audio'); $path = sys_get_temp_dir() . \DIRECTORY_SEPARATOR . $filename; file_put_contents($path, base64_decode($audioEncodedInBase64)); return $path; } } ``` ``` App\AI\Handler\WhisperAudioToTextActionHandler: tags: - { name: ibexa.ai.action.handler, priority: 0 } - { name: app.connector_ai.action.handler.audio_to_text, priority: 0 } ``` ### Integrate with the REST API At this point the custom Action Type can already be executed by using the PHP API. To integrate it with the [AI Actions execute endpoint](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#ai-actions-execute-ai-action) you need to create additional classes responsible for parsing the request and response data. See [adding custom media type](https://doc.ibexa.co/en/latest/api/rest_api/extending_rest_api/adding_custom_media_type/index.md) and [creating new REST resource](https://doc.ibexa.co/en/latest/api/rest_api/extending_rest_api/creating_new_rest_resource/index.md) to learn more about extending the REST API. #### Handle input data Start by creating an Input Parser able to handle the `application/vnd.ibexa.api.ai.TranscribeAudio` media type. ``` $data */ public function parse(array $data, ParsingDispatcher $parsingDispatcher): TranscribeAudioAction { $this->assertInputIsValid($data); $runtimeContext = $this->getRuntimeContext($data); return new TranscribeAudioAction( new AudioDataType([$data[self::AUDIO_KEY][self::BASE64_KEY]]), $runtimeContext ); } /** @param array $data */ private function assertInputIsValid(array $data): void { if (!array_key_exists(self::AUDIO_KEY, $data)) { throw new \InvalidArgumentException('Missing audio key'); } if (!array_key_exists(self::BASE64_KEY, $data[self::AUDIO_KEY])) { throw new \InvalidArgumentException('Missing base64 key'); } } /** * @param array $data */ private function getRuntimeContext(array $data): RuntimeContext { return new RuntimeContext( $data[Action::RUNTIME_CONTEXT_KEY] ?? [] ); } } ``` ``` App\AI\REST\Input\Parser\TranscribeAudio: parent: Ibexa\Rest\Server\Common\Parser tags: - { name: ibexa.rest.input.parser, mediaType: application/vnd.ibexa.api.ai.TranscribeAudio } ``` The `TranscribeAudioAction` is a value object holding the parsed request data. ``` input; } public function getRuntimeContext(): RuntimeContext { return $this->runtimeContext; } } ``` #### Handle output data To transform the `TranscribeAudioAction` into a REST response you need to create: - An `AudioText` value object holding the REST response data ``` getOutput() ); } } ``` ``` App\AI\REST\Output\Resolver\AudioTextResolver: tags: - { name: ibexa.ai.action.mime_type, key: application/vnd.ibexa.api.ai.AudioText } ``` - A visitor converting the response value object into a serialized REST response: ``` getOutput(); $generator->startObjectElement(self::OBJECT_IDENTIFIER, $mediaType); $visitor->setHeader('Content-Type', $generator->getMediaType($mediaType)); $visitor->visitValueObject($text); $generator->endObjectElement(self::OBJECT_IDENTIFIER); } } ``` ``` App\AI\REST\Output\ValueObjectVisitor\AudioText: parent: Ibexa\Contracts\Rest\Output\ValueObjectVisitor tags: - { name: ibexa.rest.output.value_object.visitor, type: App\AI\REST\Value\AudioText } ``` You can now execute a specific Action Configuration for the new custom Action Type through REST API by sending the following request: ``` POST /ai/action/execute/my_action_configuration HTTP/1.1 Accept: application/vnd.ibexa.api.ai.AudioText+json Content-Type: application/vnd.ibexa.api.ai.TranscribeAudio+json ``` ``` { "TranscribeAudio": { "Audio": { "base64": "audioEncodedInBase64" }, "RuntimeContext": { "languageCode": "eng-GB" } } } ``` ### Integrate into the back office The last step in fully integrating the Transcribe Audio Action Type embeds it directly into the back office, allowing Editors to invoke it while doing their daily work. Extend the default editing template of the `ibexa_binaryfile` fieldtype by creating a new file called `templates/themes/admin/admin/ui/fieldtype/edit/form_fields_binary_ai.html.twig`. This template embeds the AI component, but only if a dedicated `transcript` field (of `ibexa_text` type) is available in the same content type to store the content of the transcription. ``` {% extends '@ibexadesign/ui/field_type/edit/ibexa_binaryfile.html.twig' %} {% block ibexa_binaryfile_preview %} {{ parent() }} {% import '@ibexadesign/connector_ai/ui/ai_module/macros.html.twig' as ai_macros %} {% set transcriptFieldIdentifier = 'transcript' %} {% set fieldTypeIdentifiers = form.parent.parent.vars.value|keys %} {% if transcriptFieldIdentifier in fieldTypeIdentifiers %} {% set use_ai_btn_attr = { class: 'btn ibexa-btn ibexa-btn--secondary ibexa-ai-component--custom-btn', module_id: 'TranscribeAudio', scroll_selector: '.ibexa-edit-content', container_selector: '.ibexa-edit-content', input_selector: '.ibexa-field-edit-preview__action--preview', output_selector: '#ezplatform_content_forms_content_edit_fieldsData_transcript_value', ai_config_id: 'transcribe_audio', } %} {% endif %} {% endblock %} ``` And add it to the SiteAccess configuration for the `admin_group`: ``` ibexa: system: admin_group: admin_ui_forms: content_edit: form_templates: - { template: '@ibexadesign/admin/ui/fieldtype/edit/form_fields_binary_ai.html.twig', priority: -10 } ``` The configuration of the AI component takes the following parameters: - `module_id` - name of the JavaScript module to handle the invoked action. `ImgToText` is a built-in one handling alternative text use case, `TranscribeAudio` is a custom one. - `ai_config_id` - identifier of the Action Type to load Action Configurations for. The [ibexa_ai_config Twig function](https://doc.ibexa.co/en/latest/templating/twig_function_reference/ai_actions_twig_functions/#ibexa_ai_config) is used under the hood. - `container_selector` - CSS selector to narrow down the HTML area which is affected by the AI component. - `input_selector` - CSS selector indicating the input field (must be below the `container_selector` in the HTML structure). - `output_selector` - CSS selector indicating the output field (must be below the `container_selector` in the HTML structure). - `cancel_wrapper_selector` - CSS selector indicating the element to which the "Cancel AI" UI element is attached. Now create the JavaScript module mentioned in the template that is responsible for: - gathering the input data (downloading the attached binary file and converting it into base64) - executing the Action Configuration chosen by the editor through the REST API - attaching the response to the output field You can find the code of the module below. Place it in a file called `assets/js/transcribe.audio.js` ``` import BaseAIAssistantComponent from '@ibexa-connector-ai/src/bundle/Resources/public/js/core/base.ai.assistant.component'; import Textarea from '@ibexa-connector-ai-modules/ai-assistant/fields/textarea/textarea'; export default class TranscribeAudio extends BaseAIAssistantComponent { constructor(mainElement, extraConfig) { super(mainElement, extraConfig); this.requestHeaders = { Accept: 'application/vnd.ibexa.api.ai.AudioText+json', 'Content-Type': 'application/vnd.ibexa.api.ai.TranscribeAudio+json', }; this.getRequestBody = this.getRequestBody.bind(this); this.getResponseValue = this.getResponseValue.bind(this); this.replacedField = Textarea; } getAudioInBase64() { const request = new XMLHttpRequest(); request.open('GET', this.inputElement.href, false); request.overrideMimeType('text/plain; charset=x-user-defined'); request.send(); if (request.status === 200) { return this.convertToBase64(request.responseText); } } getRequestBody() { const inputValue = this.getInputValue(); const body = { TranscribeAudio: { Audio: { base64: inputValue, }, RuntimeContext: {}, }, }; if (this.languageCode) { body.TranscribeAudio.RuntimeContext.languageCode = this.languageCode; } return JSON.stringify(body); } convertToBase64(data) { let binary = ''; for (let i = 0; i < data.length; i++) { binary += String.fromCharCode(data.charCodeAt(i) & 0xff); } return btoa(binary); } getResponseValue(response) { return response.AudioText.Text.text[0]; } handleAIDialogConfirm(responseText) { this.outputElement.value = responseText; this.outputElement.dispatchEvent(new Event('input')); super.handleAIDialogClose(responseText); } } ``` The last step is adding the module to the list of AI modules in the system, by using the provided `addModule` function. Create a file called `assets/js/addAudioModule.js`: ``` import { addModule } from '@ibexa-connector-ai/src/bundle/Resources/public/js/core/create.ai.module'; import TranscribeAudio from './transcribe.audio'; addModule(TranscribeAudio); ``` And include it into the back office using Webpack Encore. See [configuring assets from main project files](https://doc.ibexa.co/en/latest/administration/back_office/back_office_elements/importing_assets_from_bundle/#configuration-from-main-project-files) to learn more about this mechanism. ``` const ibexaConfigManager = require('./ibexa.webpack.config.manager.js'); ibexaConfigManager.add({ ibexaConfig, entryName: 'ibexa-admin-ui-layout-js', newItems: [ path.resolve(__dirname, './assets/js/addAudioModule.js') ], }); ``` Your custom Action Type is now fully integrated into the back office UI and can be used by the Editors. *[Image: Transcribe Audio Action Type integrated into the back office]* ## Extend Google Gemini connector (LTS Update) The Gemini connector provides several extension points that allow you to customize available models, behavior, validation, and response handling, while remaining compatible with the AI Actions framework. The connector builds Gemini requests in an options provider and formats responses through a response formatter. Both components can be replaced or extended to customize how requests are constructed and how responses are normalized. ### Add or customize models You can register additional Gemini models or customize existing ones by extending the connector’s model [configuration](https://doc.ibexa.co/en/latest/ai_actions/configure_ai_actions/#configure-default-models). Extend the models map by defining: - a human-readable label - a `max_tokens` limit Optionally, you can set the default model that would be used for the action type that you're modifying, the default allowed tokens limit and the default temperature. Default values must stay within the limits supported by the [Gemini API](https://ai.google.dev/gemini-api/docs/models). ### Add a custom Action Handler To introduce a new Gemini-based AI action: 1. Create a handler that extends `Ibexa\Contracts\ConnectorAi\Action\AbstractActionHandler`. 2. Register the handler in `services/ai_action_handlers.yaml`. 3. Provide supporting components as needed: - a prompt factory - a form type for configuration - validators for action options This follows the same extension mechanism as other [custom AI actions](#create-custom-action-handler). ### Add custom response formatting To change how Gemini responses are post-processed or normalized: 1. Implement the `Ibexa\ConnectorGemini\Response\GeminiResponseFormatterInterface` interface. 2. Alias your implementation in the service container to override the default formatter. ### Add custom validation Add extra validation rules for Gemini action configuration options by tagging custom validators: - For `text-to-text` actions: ``` ibexa.connector_ai.action_configuration.options.validator.gemini_text_to_text ``` - For `image-to-text` actions: ``` ibexa.connector_ai.action_configuration.options.validator.gemini_image_to_text ``` ### Replace the Gemini client implementation To get full control over the low-level API communication without modifying the connector itself, you can swap the Gemini client implementation entirely with your own: - Use dependency injection to bind your own implementation to `Ibexa\ConnectorGemini\Client\GeminiClientInterface`. # Product catalog # Product Catalog The Product Catalog provides comprehensive capabilities for managing products offered in your digital commerce experience, including their specifications, pricing, and organization. Ibexa DXP offers robust product catalog infrastructure that can be used standalone. You can also use [Quable Product Information Management (PIM)](https://doc.ibexa.co/en/latest/product_catalog/quable/quable/index.md) add-on that fully integrates into the Ibexa ecosystem, or the [Remote PIM](https://doc.ibexa.co/en/latest/product_catalog/add_remote_pim_support/index.md) to add integration with any external PIM system. - [Product catalog guide](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/product_catalog/product_catalog_guide/): The product catalog guide provides a full description of the features and capabilities for managing products, their specifications, variants, pricing, and organization. - [Quable PIM Integration](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/product_catalog/quable/quable/): Quable PIM integration with Ibexa DXP - [Products](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/product_catalog/products/): Products are characterized by attributes describing their characteristics. You can create product variants and add assets to each product and variant. - [Catalogs](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/product_catalog/catalogs/): Catalogs enable filtering our a selection of products from the PIM. - [Product catalog configuration](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/product_catalog/product_catalog_configuration/): Configure product catalog settings per repository, with different catalog engines and VAT configurations. - [Prices](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/product_catalog/prices/): The price engine calculates product prices taking into account customer groups, currencies and taxes. # Product catalog guide ## What is product catalog The product catalog is a comprehensive set of capabilities for managing products in Ibexa DXP that can be used standalone. It lets you create, configure, and manage products, their specifications, assets, variants, and prices, and group products into categories and catalogs. ## Availability Product catalog capabilities are available in all Ibexa DXP editions. ## How does product catalog work Products in Ibexa DXP’s product catalog have underlying content items enriched with product-specific information such as attributes, assets, prices, and others. The product catalog lets you group products into categories and catalogs. Catalogs are collections of products selected by using configurable filters. They're specific to each of your sites or storefronts and only contain the products in them that you wish to sell in their associated storefronts. Catalogs contain a complete list of related products that can be displayed on a store site. You can have as many catalogs as required. *[Image: How does product catalog work]* ## Capabilities ### Product specifications Product specifications rely on product attributes. Available attributes are defined per product type. ### Product attributes Each product has its own, specific attributes. You can describe a product in technical terms, define its physical characteristics such as size, color, or shape, or functional characteristics (for example, for a laptop it could be the operating system, amount of memory, or available ports). Product attributes can belong to one of existing types (for example, numbers, selection, or checkout), but you can also [add custom attribute types](https://doc.ibexa.co/en/latest/product_catalog/create_custom_attribute_type/index.md). Attributes are used as criteria for filtering and searching for products. You can also configure selected product attributes to be used as a basis for variants. *[Image: Product attributes]* For more information, see [Product attributes](https://doc.ibexa.co/en/latest/product_catalog/products/#product-attributes) and [Work with product attributes](https://doc.ibexa.co/projects/userguide/en/5.0/pim/work_with_product_attributes/) ### Product variants One product can have multiple versions, for example, there can be a t-shirt in different colors. You can [create variants of products](https://doc.ibexa.co/en/latest/product_catalog/product_api/#creating-variants), differing in some characteristics, based on product attributes. *[Image: Product variants]* ### Product assets Each product or product variant can have assets in a form of images. They can be assigned to the base product or per one or more of its variants. For easier management you can create collections - by using them you can group assets that correspond to specific values of attributes. Created collection is automatically assigned to the variant or variants that have these attribute values. *[Image: Products assets]* ### Availability Product availability defines whether a product is available in the catalog. For each product you can [set availability](https://doc.ibexa.co/projects/userguide/en/5.0/pim/manage_availability_and_stock/) per variant or per base product. When a product is available, it can have numerical stock defined, that you can set. The stock can also be set to infinite, for example, for digital, downloadable products. A product can only be ordered when it has either positive stock, or stock set to infinite. ### Product categories Product categories help you to organize your products within the product catalog and also create relationships between them. Each product can belong to multiple categories of, depending on user’s choice, different or similar character. Category can also be assigned to multiple products. One of the reasons for applying product categories is assisting users in searching for products. Before you can assign categories to products, you need to [enable product categories](https://doc.ibexa.co/projects/userguide/en/5.0/pim/work_with_product_categories/#enable-product-categories). *[Image: Product categories]* ### Virtual and physical products Product types in Ibexa DXP can be either virtual or physical: - **Physical products** are tangible items that require shipping (for example: books, clothing, electronics). - **Virtual products** are items that don't require physical delivery (for example: software licenses, e-books, online courses, digital downloads, additional warranty, tickets for an event). This product type property can affect the checkout process. A cart of only virtual products skips the [shipping step](https://doc.ibexa.co/en/latest/commerce/shipping_management/shipping_management/index.md) during checkout. To learn more about working with virtual products, see [Virtual products](https://doc.ibexa.co/projects/userguide/en/5.0/pim/create_virtual_product/) in the User Documentation. ### Currencies Currencies are used when calculating product price. In the system you can find a list of available currencies, but you can also create custom ones by providing its code. ### Regions Each product or product type can have different regional pricing and regional VAT rate. You can configure regions in [YAML configuration](https://doc.ibexa.co/en/latest/product_catalog/enable_purchasing_products/#region-and-currency). ### VAT For each product you can configure VAT rate. You can set it globally (per SiteAccess) or individually for each product type and product. To set up different VAT rates for different regions (countries),you need to first configure them in [YAML configuration](https://doc.ibexa.co/en/latest/product_catalog/enable_purchasing_products/#vat-rates). ### Base price For each product or product variant you can set a base price. If you use more than one currency, in the product’s page you can see base price per currency. ### Custom price You can set up different prices depending on customer group or currency. Each customer group can have a default price discount that applies to all products. For example, you can offer a 10% discount for all products in the catalog to users who belong to the Resellers customer group. You can also set different prices for specific products or product variants for different customer groups. You can extend these capabilities even further by using [Discounts](https://doc.ibexa.co/en/latest/discounts/discounts_guide/index.md) that are available for Ibexa Commerce. ### Product completeness Created product has its own list of the tasks required for product configuration: attributes, assets, content, prices, availability, and more. You can check how complete the configuration is in the product’s view. When you create or edit a product, under the product name, you can see visual indication of what part of product information (tasks) you have completed, and what part is still missing. Product completeness doesn't impact product availability or visibility on the storefront. It is intended to help you ensure that product data is properly populated. As long as your product meets [basic requirements](https://doc.ibexa.co/en/latest/product_catalog/enable_purchasing_products/index.md), it can be published and made available for purchase regardless of its completeness score. ### Catalogs With catalogs you can create product lists for special purposes, for example, for B2B and B2C uses, for retailers and distributors, or for different regions. Catalogs contain a sub-set of products from the system. You can copy existing catalogs, for example, to create a variant version of an offer with slightly differing filters. You can then modify the copied catalog and save the updated version. ### Catalog filters and custom filter When you create a new catalog, all products are included in it by default. To have a better overview for a specific group of products, you can filter the list by: - price (Solr or Elasticsearch only) - product attributes - product type - product code - availability - product category - the date when the product was created Catalog filters let you narrow down the products from the product catalog that are available in the given catalog. Besides, the built-in catalog filters, you can also [create custom ones](https://doc.ibexa.co/en/latest/product_catalog/create_custom_catalog_filter/index.md). ### Remote PIM support Ibexa DXP provides flexible product catalog infrastructure that works with external PIM systems. In Ibexa DXP, products are created and maintained by using the REST API or the back office, and their data is stored in a local database. However, in your project or organization, you might have an existing product database, or be specifically concerned about product information security. To address such needs, Ibexa DXP provides remote PIM support. You can install and configure a readily available [Quable PIM integration](https://doc.ibexa.co/en/latest/product_catalog/quable/quable/index.md) add-on, or build a custom one to connect to a remote PIM or ERP system, pull product data and present it on your website. *[Image: Remote PIM]* An example implementation is delivered as an optional package that you can [install and customize](https://doc.ibexa.co/en/latest/product_catalog/add_remote_pim_support/index.md) to fulfill your requirements. #### Capabilities With remote PIM support, you can take advantage of the following capabilities: ##### Product marketing Use the product information coming from another system in your marketing campaigns to promote certain products or brands. By embedding the products within content items and landing pages, you can leverage Ibexa DXP marketing capabilities to showcase products. ##### Purchasing Remote PIM systems can integrate with [Commerce features](https://doc.ibexa.co/en/latest/commerce/commerce/index.md). This versatility allows for a consistent and user-friendly purchasing workflow regardless of the product's origin. ##### Pricing, stock and availability A product can only be ordered when it has defined [availability](https://doc.ibexa.co/projects/userguide/en/5.0/pim/manage_availability_and_stock/), stock and [pricing information](https://doc.ibexa.co/projects/userguide/en/5.0/pim/manage_prices/). By default, such information is held in the Ibexa DXP's local database. In your specific scenario, you can implement the support for availability and pricing information coming from an external source as well, by using a price/availability matching strategy that is an extension point exposed in the Product catalog module. #### Limitations The limitation of remote PIM depend on implementation details of specific integration and may arise in areas relying on [content model](https://doc.ibexa.co/en/latest/content_management/content_model/index.md). To see the limitations of the Quable PIM integration add-on, see [Quable PIM known limitations](https://doc.ibexa.co/en/latest/product_catalog/quable/quable_guide/#known-limitations). ##### Searching Filtering and pagination function the same as with the product catalog, relying on product attributes for effective organization of product data. However, criteria and sort clauses within product catalog relying on Ibexa DXP's content model are not supported. Depending on your source of product information, you might need to adjust the implementation to be compatible with your data format. For reference, you could review the [`CriterionVisitor` class](https://github.com/ibexa/example-in-memory-product-catalog/blob/main/src/lib/PIM/InMemory/CriterionVisitor.php) that is part of [Remote PIM example package](https://doc.ibexa.co/en/latest/product_catalog/add_remote_pim_support/#install-remote-pim-example-package). For more information about product search, see [Product Search Criteria reference](https://doc.ibexa.co/en/latest/search/criteria_reference/product_search_criteria/index.md) and [Product Sort Clauses](https://doc.ibexa.co/en/latest/search/sort_clause_reference/product_sort_clauses/index.md). ##### Catalogs Depending on the implementation, creating [catalogs](#catalogs) might be supported, but the criteria for filtering can be limited. The default implementation, which serves as a basis for the example remote PIM package, has some limitations: certain functionalities either don't operate or operate within defined constraints. Therefore, if your specific requirements aren't met, you may need to extend Ibexa DXP. ##### Editing product types, products and product attributes Editing product type, product and product attribute information stored in the remote PIM is impossible due to their read-only status. This means that, functionally speaking, communication with PIM is uni-directional, and information is pulled from a remote source but cannot be updated. ##### Content-model-based features The following features rely on Ibexa DXP's content model capabilities, which aren't supported by the default implementation of remote PIM support. Therefore, if your specific requirements aren't met, you must extend the application by using extension points exposed in the product catalog module. - Assets - Product variants - Product categories - Taxonomy - URL aliases ##### Simplified presentation of product-related blocks and views Enabling Remote PIM impacts a number of application views and blocks, such as Product view, Product list, Catalog, and Product Collection. They're simplified, for example, they don't include thumbnails and other assets, or refer to URL aliases. You can customize them by extending the default implementation. ##### Limited HTTP Caching In the context of remote PIM, it's impossible to use [content-aware HTTP caching](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/http_cache/content_aware_cache/index.md) with `ibexa_http_cache_tag_relation_ids`. ## How to get started To start working with the products, you need to enable purchasing from the catalog. For this, the following configuration is required: - at least one region and one currency added in the shop, - VAT rates set for the product type, - at least one price added for the product, - availability of the product set with positive or infinitive stock. Next, follow steps from [product management in User Documentation](https://doc.ibexa.co/projects/userguide/en/5.0/persona_paths/manage_products/). ## Benefits ### Product technical and marketing information Products added in the shop have both technical and marketing information. You can see all the attributes, specification and variants in a very detailed way, which helps you to manage and present all the products in the technical way. In addition, each product and its variants may have assets in the form of images and a description. Products have underlying content items, which means you can customize the content structure to contain all the marketing information about the product that you need. ### Detailed specification with multiple attribute types Product attributes help you to create products with detailed, complicated specification. Thanks to this, you can create product variants based on multiple product attributes that include different information about a product. Additionally, product attributes are collected in groups so they're easier to manage. *[Image: Multiple attribute types]* ### Multiple-level variants Product variants enable you to have multiple versions of one product, differing in some characteristics. Each product can have more than one variant on one or more levels. It makes it possible to have multiple-level variants of the products complicated in terms of specifications, such as laptops. *[Image: Multiple-level variants]* ### Extensible availability By default, you can configure products with specific number in stock, or with infinite availability. You can also extend the availability mechanism to cover other use cases, such as pre-orders. ### Regional pricing including regional VAT rates Each product type can have different regional pricing and regional VAT rate. What is more, you can configure VAT rate globally or set it individually. Thanks to this, the management of the products that can be sold to various markets is easier and more intuitive. *[Image: Regional pricing]* ### Customer group-based pricing You can set up different prices depending on customer group - it means that you can have a default price discount for different customer groups that applies to all the products or specific products or product variants. *[Image: Customer group-based pricing]* ### Product taxonomy The [taxonomy mechanism](https://doc.ibexa.co/en/latest/content_management/taxonomy/taxonomy/index.md) enables creating tags or categories with a tree structure and assign them to a content item, for example, Products. Thanks to this mechanism product categories can be organized into a Category tree to make it easy for the users to browse and to deliver content appropriate for them. ### Grouping products into catalogs You can group all the products into smaller catalogs. They contain subsets of the whole product list and you can use them to build special catalogs, for example, for retailers and distributors, or for different regions. *[Image: Grouping products into catalogs]* ### General and variant-specific assets Products and product variants can have their image assets. You can set up general assets — it means that the product has an asset visible in the main product view. Additionally, you can assign assets to product variants and place them in a collection. *[Image: General and variant-specific assets]* # Quable PIM Integration Ibexa DXP integrates with [Quable](https://www.quable.com/en) to provide product information management as part of the Ibexa orchestration platform. Quable is Ibexa’s PIM solution for managing complex product catalogs and serves as the single source of truth, available as an add-on for Ibexa DXP. Once you install and configure it, the integration performs an initial synchronization of product data, followed by ongoing updates via webhooks. Products can be viewed, selected, and embedded in Ibexa DXP, while all product management operations remain handled in Quable. ## Getting started - [Quable product guide](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/product_catalog/quable/quable_guide/): The Quable product guide describes how you can use the product data from Quable in Ibexa DXP to create marketing campaigns built around your products. - [Quable PIM integration](https://doc.ibexa.co/projects/userguide/en/5.0/product_catalog/quable_pim_integration/): Quable PIM integration allows you to use products managed in Quable as the source of product data in Ibexa DXP. - [Quable - PIM solution for product data management](https://quable.com/en): Manage your product data and accelerate sales with Quable. Discover the new PIM platform that revolutionizes the product experience - [Quable resources](https://docs.quable.com/): Find all Quable PIM, DAM, and Portal resources: user guides, training content, product documentation, technical documentation, and the PIM API for developers. ## Development - [Install Quable connector](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/product_catalog/quable/install_quable/): Install and configure Quable PIM connector for Ibexa DXP - [Configure Quable connector](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/product_catalog/quable/configure_quable_connector/): Quable PIM connector configuration reference for Ibexa DXP - [Quable API](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/product_catalog/quable/quable_api/): Learn how to use PHP and REST APIs to retrieve product data from Quable - [Quable Twig functions](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/templating/twig_function_reference/quable_twig_functions/): Twig functions exposed by the Quable connector. - [Customize product attribute templates](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/product_catalog/customize_product_attribute_templates/): Customize the Twig templates used to render product attribute values. - [Quable technical documentation](https://developers.quable.com/): Explore Quable's technical documentation # Quable product guide ## Overview Quable integration connects Ibexa DXP with [Quable Product Information Management (PIM)](https://www.quable.com/en), making Quable the authoritative source of product information for every website powered by Ibexa DXP. Quable serves as the single source of truth for all product data, including attributes, classifications, variants, and translations. Ibexa DXP consumes this data and makes it available for use in content and digital experiences. This approach eliminates the need to manage product data in multiple systems, while preserving a clear separation of responsibilities between product management and content usage. ## Availability The integration with Quable PIM is available as an add-on for all Ibexa DXP editions, starting with Ibexa DXP v5.0.7. Before installing and enabling the add-on, ensure that you have an active Quable PIM instance with defined products, classifications, and channels. Then, [perform the initial configuration](https://doc.ibexa.co/en/latest/product_catalog/quable/install_quable/index.md). ## How does Quable integration work The integration is built on Ibexa DXP's [Remote PIM framework](https://doc.ibexa.co/en/latest/product_catalog/add_remote_pim_support/index.md), which enables connection to external product data sources. Once configured, the system performs: - an initial synchronization of product data from Quable - ongoing updates via webhooks (near real-time) Product data is mapped to the Ibexa DXP's product data model, including variants, attributes and [product categorties](https://doc.ibexa.co/en/latest/product_catalog/product_catalog_guide/#product-taxonomy). This data is then available in the back office, content editing tools like [Online Editor](https://doc.ibexa.co/en/latest/content_management/rich_text/online_editor_guide/index.md) and [Page Builder](https://doc.ibexa.co/en/latest/content_management/pages/page_builder_guide/index.md), and APIs. All product management operations remain handled in Quable. Ibexa DXP can be used to manage pricing and availability for products sourced from Quable, including support for market-specific configurations such as regions and currencies. ## Capabilities ### Single source of truth Quable is the authoritative system for product data, including attributes, classifications, variants, and translations. Ibexa DXP consumes this data and makes it available for use within content and back office interfaces, enabling editorial teams to enrich content by reusing product information. ## Use cases ### Multi-market operations A retailer operating across multiple markets can manage product data in Quable using channels and localized languages. Ibexa DXP connects to the relevant channel and makes localized product information available for use in content and back office interfaces, ensuring consistency across markets from a single Quable instance. ## Faster campaign execution Product data defined in Quable can be immediately used in Ibexa DXP for building content and campaigns. Marketing teams can create pages and enrich content using up-to-date product information, without the need to duplicate or manually synchronize data. ## Known limitations The integration with Quable has the following known limitations: - It's not compatible with [Commerce](https://doc.ibexa.co/en/latest/commerce/commerce/index.md) functionalities. [Carts](https://doc.ibexa.co/en/latest/commerce/cart/cart/index.md), [order management](https://doc.ibexa.co/en/latest/commerce/order_management/order_management/index.md), and [shopping lists](https://doc.ibexa.co/en/latest/commerce/shopping_list/shopping_list/index.md) can't be used with products coming from Quable. - [Catalogs](https://doc.ibexa.co/en/latest/product_catalog/product_catalog_guide/#catalogs) can't be created from Quable products. - [Product assets](https://doc.ibexa.co/en/latest/product_catalog/product_catalog_guide/#product-assets) are not fully synchronized. Only the main product thumbnail from Quable is used. - [Product-level access restrictions](https://doc.ibexa.co/en/latest/permissions/policies/#products) based on product type are not supported. - You can't define prices and availability for products with product codes exceeding 64 characters. # Install Quable connector To integrate Ibexa DXP with Quable PIM, you need to install the Quable connector packages, configure the connection, and set up synchronization. ## Create Quable instance Before installing the Quable connector, ensure you have access to a [Quable PIM instance](https://quable.com). ## Install package Run the following command to install the required package: ``` composer require ibexa/connector-quable ``` The command adds the Quable connector code, including services that enable communication with Quable PIM. ## Get API credentials To connect to Quable PIM, you need an API token: 1. Log in to your Quable instance, for example, `https://example.quable.com`. 2. Navigate to the [API Tokens](https://docs.quable.com/v5-EN/docs/api-tokens) section. 3. Create a new **Read Access Token** for use in the configuration. ## Configure Quable connector In `config/packages/ibexa_connector_quable.yaml`, specify the configuration for the Quable connector: ``` ibexa_connector_quable: instance_url: 'https://example.quable.com' api_token: '' channel_code: '' ``` Replace `` with the Read Access API token you obtained from Quable in the previous step. [Quable's channels](https://docs.quable.com/v5-EN/docs/content-channels) allow you to distribute your product information to defined recipients, for example e-commerce platforms. Select the Quable channel that you want to integrate within Ibexa DXP. For all available configuration options, see [Configure Quable](https://doc.ibexa.co/en/latest/product_catalog/quable/configure_quable_connector/index.md). ## Configure product catalog engine To use Quable as a product data source, configure Ibexa DXP's [product catalog](https://doc.ibexa.co/en/latest/product_catalog/product_catalog_guide/index.md) to use the Quable engine. ### Define Quable engine In `config/packages/ibexa_product_catalog.yaml`, add a new engine configuration: ``` ibexa_product_catalog: engines: local: type: local options: root_location_remote_id: ibexa_product_catalog_root product_type_group_identifier: product quable: type: quable options: taxonomy: quable root_location_remote_id: ibexa_product_catalog_root product_type_group_identifier: product ``` This configuration defines two engines: the default `local` engine and the new `quable` engine, allowing you to work with products defined within Quable. To learn more about product catalog configuration, see [Product catalog configuration](https://doc.ibexa.co/en/latest/product_catalog/product_catalog_configuration/index.md). The Quable integration add-on comes with a new [taxonomy](https://doc.ibexa.co/en/latest/content_management/taxonomy/taxonomy/index.md) called `quable`. By setting the `ibexa_product_catalog.engines.quable.options.taxonomy` key to `quable`, you configure the engine to use it for storing product categories. ### Set Quable as default engine In your repository configuration, typically in `config/packages/ibexa.yaml`, configure the product catalog to use the Quable engine as the product data source: ``` ibexa: repositories: default: storage: ~ search: engine: '%search_engine%' connection: default product_catalog: engine: quable regions: default: ~ ``` ## Set up languages To use the products from Quable within Ibexa DXP content, make sure the [data languages](https://docs.quable.com/v5-EN/docs/data-languages) in Quable have corresponding [languages](https://doc.ibexa.co/en/latest/multisite/languages/languages/index.md) in Ibexa DXP. To compare the language configuration in both systems, run the following command: ``` php bin/console ibexa:quable:languages:check ``` Based on the command output, configure the `language_map` in `config/packages/ibexa_connector_quable.yaml`, mapping each Ibexa DXP language code to its Quable locale code as in the following example: ``` ibexa_connector_quable: # ... language_map: eng-GB: en_GB fre-FR: fr_FR ``` The system uses the language map to retrieve data in the correct language from Quable. After configuring the map, rerun the `ibexa:quable:languages:check` command to confirm all languages are correctly mapped. ## Synchronize taxonomy After configuring the integration, synchronize [product classifications from Quable](https://docs.quable.com/v5-EN/docs/documents-classification-new-version) to Ibexa DXP's [taxonomies](https://doc.ibexa.co/en/latest/content_management/taxonomy/taxonomy/index.md). Run the following command to synchronize classifications: ``` php bin/console ibexa:quable:classification:sync ``` This command imports the product classification structure from Quable PIM into Ibexa DXP, ensuring that product categories are aligned. > **Tip: Tip** > > To keep the classifications aligned, we recommend running the `ibexa:quable:classification:sync` command every night, even when using synchronization with webhooks. ## Set up real-time synchronization Quable PIM can notify Ibexa DXP about product data and classification changes in real-time by using webhooks. This invalidates the cache kept in Ibexa DXP, ensuring that product information stays up to date. Webhook configuration must be set up in both Quable PIM and Ibexa DXP. ### Create webhook in Quable 1. Create a new [webhook in Quable](https://docs.quable.com/v5-EN/docs/webhook). 1. Set the webhook code (used as the webhook name). 1. Provide the URL to your Ibexa DXP instance suffixed by `/webhook/quable`, for example: `https://example.com/webhook/quable`. 1. Mark it as **Activated**. 1. Enter a secret value for the **Authorization Header**. 1. Choose the following scopes: - Products: created, updated, deleted - Classifications: created, updated, deleted The **Authorization Header** value is a [secret that must be kept secure](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/security/security_checklist/#app_secret-and-other-secrets). > **Note: Note** > > For local development and testing, you can consider using one of the available [tunnel providers](https://github.com/anderspitman/awesome-tunneling) to make your local instance accessible from the internet. ### Configure webhook in Ibexa DXP In `config/packages/ibexa_connector_quable.yaml`, specify the configuration for the Quable connector: ``` ibexa_connector_quable: # ... webhook_secret: '' ``` > **Warning: Warning** > > [Quable uses dynamic IP addresses](https://faq.quable.com/en/articles/8250056-what-are-the-ip-addresses-of-quable-to-add-to-the-whitelist) to connect to Ibexa DXP. If your DXP instance is protected by a firewall, make sure your configuration allows connections from changing IP addresses. ### Configure background task Ibexa DXP's webhook processes Quable's classification change events and queues them to be processed in the background. To process them, [configure Ibexa Messenger](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/background_tasks/index.md) and make sure the `messenger:consume` command is run periodically. # Configure Quable connector You can customize the behavior of the Quable integration add-on by using the following [configuration](https://doc.ibexa.co/en/latest/administration/configuration/configuration/index.md). ## Configuration example In `config/packages/ibexa_connector_quable.yaml`, specify your configuration by using the `ibexa_connector_quable` key: ``` ibexa_connector_quable: enabled: true instance_url: 'https://example.quable.com' api_token: '' channel_code: '' webhook_secret: '' # Needed for webhook authentication language_map: eng-GB: en_GB fre-FR: fr_FR throw_on_invalid_criteria: '%kernel.debug%' throw_on_invalid_mapping: '%kernel.debug%' cache: enabled: true attribute: true attribute_group: true product: true product_type: true ``` ## Configuration options | Parameter | Default value | Description | | --------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `enabled` | `false` | Enables the connector. | | `instance_url` | string | Base URL of your Quable instance, for example `https://example.quable.com`. | | `api_token` | string | [Read Access API token](https://docs.quable.com/v5-EN/docs/api-tokens) used to authenticate requests to Quable. | | `channel_code` | string | Code of the [Quable channel](https://docs.quable.com/v5-EN/docs/content-channels) used as the source of product data. | | `webhook_secret` | string | Secret expected in the [webhook](https://docs.quable.com/v5-EN/docs/webhook) authorization header. | | `language_map` | Empty | Maps Ibexa DXP language codes (for example, `eng-GB`) to Quable locale codes (for example, `en_GB`). For more information, see [Set up Quable languages](https://doc.ibexa.co/en/latest/product_catalog/quable/install_quable/index.md) | | `throw_on_invalid_criteria` | `%kernel.debug%` | Controls behavior for unsupported search criteria: `true` throws an exception, `false` only logs unsupported criteria. | | `throw_on_invalid_mapping` | `%kernel.debug%` | Controls behavior for mapping errors during data transformation: `true` throws an exception, `false` only logs mapping errors. | | `cache.enabled` | `true` | Global cache switch for the connector. When set to `false`, only [in-memory cache](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/persistence_cache/#in-memory-cache-configuration) is used. When set to `true`, [Symfony's `cache.app` cache pool](https://symfony.com/doc/7.4/cache.html#system-cache-and-application-cache) is used. | | `cache.attribute` | `true` | Enables caching for attribute definition requests. | | `cache.` `attribute_group` | `true` | Enables caching for attribute group requests. | | `cache.` `product` | `true` | Enables caching for product requests. | | `cache.` `product_type` | `true` | Enables caching for product type requests. | In production environments, it's recommended to: - keep the `api_token` and the `webhook_secret` [secure](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/security/security_checklist/#app_secret-and-other-secrets) - enable caching for better performance, by using Redis or Valkey as [persistence cache](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/persistence_cache/#redisvalkey) - disable `throw_on_invalid_criteria` and `throw_on_invalid_mapping` to prevent non-critical errors from causing application crashes # Quable API As Quable products are represented as Ibexa DXP products, you can use the existing [Product APIs](https://doc.ibexa.co/en/latest/product_catalog/product_api/index.md) to retrieve the product information. Quable is the source of truth about products and categories and you should only use the Ibexa DXP APIs to read the information coming from Quable, but you can't use them to modify it. To modify the information, use the [Quable interface](https://quable.com) or the dedicated [Quable APIs](https://developers.quable.com/quable-api/). ## REST API Usage To learn how to work with Ibexa DXP REST API, see [REST API reference](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_usage/rest_api_usage/index.md). You can use the following endpoints to retrieve product and category information: - [Product REST API](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Product) - [Taxonomy REST API](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Taxonomy) ## PHP API Usage ### Retrieve products To retrieve product information coming from Quable, use the same APIs as described in [Product API](https://doc.ibexa.co/en/latest/product_catalog/product_api/index.md). The following example shows how you can retrieve a single product: ``` $product = $this->productService->getProduct($productCode); $output->writeln('Product with code ' . $product->getCode() . ' is ' . $product->getName()); ``` ### Search for products Use [`ProductQuery`](https://doc.ibexa.co/en/latest/product_catalog/product_api/#getting-product-information) to search for mulitple products: ``` $criteria = new Criterion\ProductType([$productType]); $sortClauses = [new SortClause\ProductName(ProductQuery::SORT_ASC)]; $productQuery = new ProductQuery(null, $criteria, $sortClauses); $products = $this->productService->findProducts($productQuery); foreach ($products as $product) { $output->writeln($product->getName() . ' of type ' . $product->getProductType()->getName()); } ``` When working with Quable products, the following search criteria are supported: | Search Criterion | Search based on | | ---------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | | [CreatedAt](https://doc.ibexa.co/en/latest/search/criteria_reference/createdat_criterion/index.md) | Date and time when product was created | | [LogicalAnd](https://doc.ibexa.co/en/latest/search/criteria_reference/logicaland_criterion/index.md) | Composite criterion combining multiple criteria with AND | | [MatchAll](https://doc.ibexa.co/en/latest/search/criteria_reference/matchall_criterion/index.md) | All products | | [ProductCategory](https://doc.ibexa.co/en/latest/search/criteria_reference/productcategory_criterion/index.md) | Product category assigned to product | | [ProductCategorySubtree](https://doc.ibexa.co/en/latest/search/criteria_reference/productcategorysubtree_criterion/index.md) | Product category subtree | | [ProductCode](https://doc.ibexa.co/en/latest/search/criteria_reference/productcode_criterion/index.md) | Product's code | | [ProductName](https://doc.ibexa.co/en/latest/search/criteria_reference/productname_criterion/index.md) | Product's name | | [ProductType](https://doc.ibexa.co/en/latest/search/criteria_reference/producttype_criterion/index.md) | Product type | | [UpdatedAt](https://doc.ibexa.co/en/latest/search/criteria_reference/updated_at_criterion/index.md) | Date and time when product was last updated | The following sort clauses are supported: | Sort Clause | Sorting based on | | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------ | | [CreatedAt](https://doc.ibexa.co/en/latest/search/sort_clause_reference/createdat_sort_clause/index.md) | Date and time of the creation of a product | | [ProductCode](https://doc.ibexa.co/en/latest/search/sort_clause_reference/productcode_sort_clause/index.md) | Product's code | | [ProductName](https://doc.ibexa.co/en/latest/search/sort_clause_reference/productname_sort_clause/index.md) | Product's name | ### Manage stock and pricing For information stored outside of Quable, such as [product availability](https://doc.ibexa.co/en/latest/product_catalog/product_api/#product-availability) or [pricing](https://doc.ibexa.co/en/latest/product_catalog/price_api/index.md), you can use the existing services to manage them: ``` // Manage availability $product = $this->productService->getProduct('NEWMODIFIEDPRODUCT'); $productAvailabilityCreateStruct = new ProductAvailabilityCreateStruct($product, true, true); $this->productAvailabilityService->createProductAvailability($productAvailabilityCreateStruct); // Manage prices $newCurrency = $this->currencyService->getCurrencyByCode($newCurrencyCode); $money = new Money\Money(50000, new Money\Currency($newCurrencyCode)); $priceCreateStruct = new ProductPriceCreateStruct($product, $newCurrency, $money, null, null); $this->productPriceService->createProductPrice($priceCreateStruct); ``` For advanced pricing strategies, use the [Discounts API](https://doc.ibexa.co/en/latest/discounts/discounts_api/index.md) to specify prices for Quable's products. # Product catalog configuration You can configure the product catalog per [Repository](https://doc.ibexa.co/en/latest/administration/configuration/repository_configuration/index.md). Under `ibexa.repositories..product_catalog` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files), indicate the catalog engine to use: ``` ibexa: repositories: default: storage: ~ search: engine: '%search_engine%' connection: default product_catalog: engine: 'default' ``` The `default` engine is available out of the box, and configured under `ibexa_product_catalog`: ``` ibexa_product_catalog: engines: default: type: local options: root_location_remote_id: e5ce2e391bd94e26a5cd88746f24ecce product_type_group_identifier: 'product' ``` The `local` type is the built-in type of catalog based on the content repository. With [Quable PIM integration](https://doc.ibexa.co/en/latest/product_catalog/quable/quable_guide/index.md) add-on installed and configured, by using the `quable` type you can retrieve product data coming from Quable. You can use a single engine across all repositories, or assign different ones per repository. Each repository can use only one product catalog engine. Under `options.product_type_group_identifier` you can define the identifier of the content type Group used for storing products. `root_location_remote_id` indicates the remote ID of the location where products are stored. ## VAT rates To set up different VAT rates for different regions (countries), you can use the following configuration under the `ibexa.repositories..product_catalog.regions` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files), for example: ``` ibexa: repositories: : product_catalog: engine: default regions: : vat_categories: standard: value: 18 extras: : reduced: value: 6 zero: value: 0 none: value: ~ ``` VAT rates configuration accepts additional flags under the `extras` key. It's an extension point that you can build upon to add custom functionalities. You can use it, for example, to pass additional information to the UI or define region-specific exclusions when calculating the tax values. For each VAT category value, setting a value to "null" (~) is equal to making the following setting: ``` none: value: 0 extras: not_applicable: true ``` ## Code generation strategy Product codes for variants are generated automatically based on the selected strategy. The following strategies are available: - `incremental` (default) - variant code consists of base product code plus index, for example: `ErgoDesk-1`, `ErgoDesk-2`. - `random` - variant code consists of base product code plus random string of characters, for example: `ErgoDesk-62E7B3379AEB4`, `ErgoDesk-62E7B3379AFBC` You can choose the strategy with the following configuration: ``` ibexa_product_catalog: engines: default: type: local options: root_location_remote_id: ibexa_product_catalog_root product_type_group_identifier: 'product' variant_code_generator_strategy: 'random' ``` You can also [create your own custom code generation strategy](https://doc.ibexa.co/en/latest/product_catalog/create_product_code_generator/index.md). ## Attribute rendering templates You can configure which Twig templates are used to render product attribute values with the [`ibexa_format_product_attribute` Twig filter](https://doc.ibexa.co/en/latest/templating/twig_function_reference/product_twig_functions/#ibexa_format_product_attribute). ``` ibexa_product_catalog: templates: attributes: - 'templates/product/attributes/my_attribute_blocks.html.twig' ``` The default template (`@ibexadesign/product_catalog/product/attributes/attribute_blocks.html.twig`) is always appended as the last fallback, even if not listed explicitly. For more information, see [Customize product attribute templates](https://doc.ibexa.co/en/latest/product_catalog/customize_product_attribute_templates/index.md). ## Catalogs ### Catalog filters You can configure which [catalog filters](https://doc.ibexa.co/en/latest/product_catalog/catalogs/index.md) are applied by default with the following configuration: ``` ibexa: system: admin: product_catalog: catalogs: default_filters: - product_code - product_availability ``` The order of filters in this configuration reflects the order in which they're displayed in the back office. # Products Products are a special type of content that contains typical content Fields and additional product information. Each product belongs to a product type (similar to how a content item belongs to a content type). Each product has a unique identifying product code. Product code can have up to 64 characters. It can contain only letters, numbers, underscores, and dashes. ## Product types Product types represent categories that a product can belong to. A product type can be, for example, a sofa, or a keyboard. Product types, like content types, define the global properties of products and fields a product consists of. A product type also defines the attributes that all products of this type can have. You can choose between two available types: `physical` and `virtual`: - `physical` - tangible products with assigned stock. They can use measurement attributes. They require shipment in the online purchase process. Examples: heaters, laptops, phones. - `virtual` - non-tangible items. They can be sold individually, or as part of a product bundle. They don't require shipment in the online process. Examples: memberships, services, warranties. ## Product attributes Product attributes provide different information about a product and can be used to create [product variants](#product-variants). Typical product attribute examples are: length, weight, color, format, and more. The following attribute types are available: - checkbox - color - [date and time](https://doc.ibexa.co/en/latest/product_catalog/attributes/date_and_time/index.md) - float - integer - measurement (`measurement_range` and `measurement_single`) - selection - [symbol](https://doc.ibexa.co/en/latest/product_catalog/attributes/symbol_attribute_type/index.md) Product attributes are collected in groups. An example of an attribute group can be dimensions (length, width, height). You can assign both whole attribute groups or individual attributes to a product type. > **Note: Attribute translations** > > Product attributes are not translatable. Unlike content fields, product attribute values cannot differ between languages. > > For the information that is intended to be displayed, consider using [TextLine](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/textlinefield/index.md) fields for short text, [RichText](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/richtextfield/index.md) fields for longer text that may require formatting, and product attributes for precise product properties or specifications. ## Product variants Product variants represent different versions of a product, for example, clothes in different colors, or laptops with different amounts of RAM. You can create product variants automatically based on attributes that have the "Used for product variants" flag enabled in the product type definition. You can create variants for any combination of values of selected attributes. In the back office you can automatically generate all possible variants for a product. Codes for product variants are generated automatically based on the [selected strategy](https://doc.ibexa.co/en/latest/product_catalog/product_catalog_configuration/#code-generation-strategy). Each product variant has separate availability and stock information. Each variant can also have separate price rules. If a variant doesn't have separate price rules, it uses the price of its base product. ## Product assets Product assets are images that are assigned to products and their specific variants. You can group assets in collections which correspond to specific values of attributes. A collection is assigned to the variant or variants that have these attribute values. ## Embed products in content You can embed products directly into content, including the [landing pages](https://doc.ibexa.co/en/latest/content_management/pages/pages/index.md), by using the [Online Editor](https://doc.ibexa.co/en/latest/content_management/rich_text/online_editor_guide/index.md). Use it to build marketing campaigns directly around the products, bridging product marketing and product data together. To customize the design of the embedded products, see [Customize product embed templates](https://doc.ibexa.co/en/latest/product_catalog/customize_product_embed_templates/index.md). ## Product availability and stock Product availability defines whether a product is available in the catalog. You set product availability per variant or per base product: - if a product cannot have variants (has no attributes with the "Used for product variants" flag), you set availability per base product - if a product can have variants (even if no variants are configured yet), you set availability per variant. When a product is available, it can have numerical stock defined. The stock can also be set to infinite (for example, in case of digital products). > **Note: Note** > > Availability doesn't automatically mean that a product can be ordered. A product can be available, but have zero stock. > > A product can only be ordered when it has either positive stock, or stock set to infinite. # Date and time attributes The date and time [attribute type](https://doc.ibexa.co/en/latest/product_catalog/products/#product-attributes) allows you to represent date and time values as part of the product specification in the [product catalog](https://doc.ibexa.co/en/latest/product_catalog/product_catalog_guide/index.md). You can use it to store, for example, manufacturing dates, expiration dates, or event dates, all with specified accuracy. ## Usage You can manage the date and time attribute type through the back office, [data migrations](https://doc.ibexa.co/en/latest/content_management/data_migration/importing_data/#date-and-time-attributes), REST, or through the PHP API. It also supports [searching](https://doc.ibexa.co/en/latest/search/criteria_reference/product_search_criteria/index.md) by using [DateTimeAttribute](https://doc.ibexa.co/en/latest/search/criteria_reference/datetimeattribute_criterion/index.md) and [DateTimeAttributeRange](https://doc.ibexa.co/en/latest/search/criteria_reference/datetimeattributerange_criterion/index.md) criteria. *[Image: Creating a product using a date and time attribute with "trimester" accuracy level]* When creating an attribute based on the date and time attribute type you can select the accuracy level to match your needs: | Accuracy | Example | Limitations | | --------- | ------------------- | ---------------------------- | | Year | 2025 | Number between 1000 and 9999 | | Trimester | Q3 2025 | | | Month | July 2025 | | | Day | 2025-07-06 | | | Minute | 2025-07-06 11:15 | | | Second | 2025-07-06 11:15:37 | | # Symbol attribute type In product specifications, the symbol attribute type enables the efficient representation of string-based data and enforces their format. This feature allows you to store standard product identifiers (such as EAN or ISBN) in the [product catalog](https://doc.ibexa.co/en/latest/product_catalog/product_catalog_guide/index.md). ## Build-in symbol attribute formats The built-in symbol attribute formats in `ibexa/product-catalog-symbol-attribute` are listed below: | Name | Description | Example | | -------------------------------------- | ----------------------------------------------------------------------------------------- | ----------------- | | Generic | Accepts any string value | #FR1.2 | | Generic (alphabetic characters only) | Accepts any string value that contains only letters | ABCD | | Generic (digits only) | Accepts any string value that contains only digits | 123456 | | Generic (alphanumeric characters only) | Accepts any string value that contains only letters or digits | 2N6405G | | Generic (hexadecimal digits only) | Accepts any string value that contains only hexadecimal digits (digits or A-F characters) | DEADBEEF | | EAN-8 | European Article Number (8 characters) | 96385074 | | EAN-13 | European Article Number (13 characters) | 5023920187205 | | EAN-14 | European Article Number (14 characters) | 12345678901231 | | ISBN-10 | International Standard Book Number (10 characters) | 0-19-852663-6 | | ISBN-13 | International Standard Book Number (13 characters) | 978-1-86197-876-9 | > **Caution: Caution** > > Maximum length of the symbol value is 160 characters. ## Create custom symbol attribute format Under the `ibexa_product_catalog_symbol_attribute.formats` key, you can use configuration to create your own symbol format. See the example below: ``` ibexa_product_catalog_symbol_attribute: formats: manufacturer_part_number: name: 'Manufacturer Part Number' pattern: '/^[A-Z]{3}-\d{5}$/' examples: - 'RPI-14645' - 'MSS-24827' - 'SEE-15444' ``` This following example specifies the format for a "Manufacturer Part Number", defined with the `manufacturer_part_number` identifier. The pattern is specified using a regular expression. According to the pattern option, the attribute value: - must be a string - begins with three capital letters (A-Z), followed by a hyphen ("-") - ends with five digits (0-9), with no other characters before or after Certain formats, such as the International Standard Book Number (ISBN-10) and the European Article Number (EAN-13), contain checksum digits and are self-validating. To validate checksum of symbol: 1. Create a class implementing the [`\Ibexa\Contracts\ProductCatalogSymbolAttribute\Value\ChecksumInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalogSymbolAttribute-Value-ChecksumInterface.html) interface. 1. Register the class as a service using the `ibexa.product_catalog.attribute.symbol.checksum` tag and specify the format identifier using the `format` attribute. See below the example implementation of checksum validation using Luhn formula: ``` getDigits($value); $count = count($digits); $total = 0; for ($i = $count - 2; $i >= 0; $i -= 2) { $digit = $digits[$i]; if ($i % 2 === 0) { $digit *= 2; } $total += $digit > 9 ? $digit - 9 : $digit; } $checksum = $digits[$count - 1]; return $total + $checksum === 0; } /** * Returns an array of digits from the given value (skipping any formatting characters). * * @return int[] */ private function getDigits(string $value): array { $chars = array_filter( str_split($value), static fn (string $char): bool => $char !== '-' ); return array_map(intval(...), array_values($chars)); } } ``` Example service definition: ``` services: App\PIM\Symbol\Format\Checksum\LuhnChecksum: tags: - name: ibexa.product_catalog.attribute.symbol.checksum format: my_format ``` The format attribute (`my_format`) is the identifier used under the `ibexa_product_catalog_symbol_attribute.formats` key. ## Search for products with given symbol attribute You can use `SymbolAttribute` Search Criterion to find products by symbol attribute: For more information, see [SymbolAttribute Criterion](https://doc.ibexa.co/en/latest/search/criteria_reference/symbolattribute_criterion/index.md). # Product API ## Products Ibexa DXP's Product API provides two services for handling product information, which differ in function: | Service name | Description | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [`ProductServiceInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-ProductServiceInterface.html) | Use it to retrieve product data regardless of the source: Ibexa DXP, [Quable](https://doc.ibexa.co/en/latest/product_catalog/quable/quable/index.md), or [remote PIM](https://doc.ibexa.co/en/latest/product_catalog/add_remote_pim_support/index.md) | | [`LocalProductServiceInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Local-LocalProductServiceInterface.html) | Use it to modify products defined in Ibexa DXP | > **Tip: Product REST API** > > To learn how to load products using the REST API, see [REST API reference](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Product/operation/api_productcatalogproductsview_post). ### Getting product information Get an individual product by using the `ProductServiceInterface::getProduct()` method: ``` $product = $this->productService->getProduct($productCode); $output->writeln('Product with code ' . $product->getCode() . ' is ' . $product->getName()); ``` Find multiple products with `ProductServiceInterface::findProducts()`. Provide the method with optional filter, query or Sort Clauses. ``` $criteria = new Criterion\ProductType([$productType]); $sortClauses = [new SortClause\ProductName(ProductQuery::SORT_ASC)]; $productQuery = new ProductQuery(null, $criteria, $sortClauses); $products = $this->productService->findProducts($productQuery); foreach ($products as $product) { $output->writeln($product->getName() . ' of type ' . $product->getProductType()->getName()); } ``` See [Product Search Criteria](https://doc.ibexa.co/en/latest/search/criteria_reference/product_search_criteria/index.md) and [Product Sort Clauses](https://doc.ibexa.co/en/latest/search/sort_clause_reference/product_sort_clauses/index.md) references for more information about how to use the [`ProductQuery`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-ProductQuery.html) class. ### Modifying products To create, update and delete products, use the `LocalProductServiceInterface`. ``` $productUpdateStruct = $this->localProductService->newProductUpdateStruct($product); $productUpdateStruct->setCode('NEWMODIFIEDPRODUCT'); $this->localProductService->updateProduct($productUpdateStruct); ``` To create a product, use `LocalProductServiceInterface::newProductCreateStruct()` to get a [`ProductCreateStruct`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Local-Values-Product-ProductCreateStruct.html). Provide the method with the product type object and the main language code. You also need to set (at least) the code for the product and the required Field of the underlying content type, `name`: ``` $productType = $this->productTypeService->getProductType($productType); $createStruct = $this->localProductService->newProductCreateStruct($productType, 'eng-GB'); $createStruct->setCode('NEWPRODUCT'); $createStruct->setField('name', 'New Product'); $this->localProductService->createProduct($createStruct); ``` To delete a product, use `LocalProductServiceInterface::deleteProduct()`: ``` $this->localProductService->deleteProduct($product); ``` ### Product variants #### Searching for variants of a specific product You can access the variants of a product by using the [`ProductServiceInterface::findProductVariants()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-ProductServiceInterface.html#method_findProductVariants) method. The method takes the product object and a [`ProductVariantQuery`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-ProductVariantQuery.html) object as parameters. You can filter variants by: - variant codes: ``` // Get variants filtered by variant codes $codeQuery = new ProductVariantQuery(); $codeQuery->setVariantCodes(['DESK-red', 'DESK-blue']); $specificVariants = $this->productService->findProductVariants($product, $codeQuery)->getVariants(); ``` - product criteria: To use [Product Search Criteria](https://doc.ibexa.co/en/latest/search/criteria_reference/product_search_criteria/index.md) with [`ProductVariantQuery`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-ProductVariantQuery.html), wrap it with the [`ProductCriterionAdapter`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Content-Query-Criterion-ProductCriterionAdapter.html) class, as in the example below: ``` // Get variants with specific attributes $combinedQuery = new ProductVariantQuery(); $combinedQuery->setAttributesCriterion( new ProductCriterionAdapter( new Criterion\LogicalAnd([ new Criterion\ColorAttribute('color', ['red', 'blue']), new Criterion\IntegerAttribute('size', 42), ]) ) ); $filteredVariants = $this->productService->findProductVariants($product, $combinedQuery)->getVariants(); ``` From a variant ([`ProductVariantInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-ProductVariantInterface.html)), you can access the attributes that are used to generate the variant by using the [`ProductVariantInterface::getDiscriminatorAttributes()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-ProductVariantInterface.html#method_getDiscriminatorAttributes) method. ``` $attributes = $variant->getDiscriminatorAttributes(); foreach ($attributes as $attribute) { $output->writeln($attribute->getIdentifier() . ': ' . $attribute->getValue() . ' '); } ``` #### Searching for variants across all products To search for variants across all products, use the [`ProductServiceInterface::findVariants()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-ProductServiceInterface.html#method_findVariants) method. This method takes a [`ProductVariantQuery`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-ProductVariantQuery.html) object and returns variants regardless of their base product. Unlike `findProductVariants()`, which requires a specific product object, `findVariants()` allows you to search the entire variant catalog. You can filter variants by: - variant codes: ``` // Search variants across all products $query = new ProductVariantQuery(); $query->setVariantCodes(['DESK-red', 'DESK-blue']); $variantList = $this->productService->findVariants($query); ``` - product criteria: To use [Product Search Criteria](https://doc.ibexa.co/en/latest/search/criteria_reference/product_search_criteria/index.md) with [`ProductVariantQuery`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-ProductVariantQuery.html), wrap it with the [`ProductCriterionAdapter`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Content-Query-Criterion-ProductCriterionAdapter.html) class, as in the example below: ``` // Search variants with attribute criterion $colorQuery = new ProductVariantQuery(); $colorQuery->setAttributesCriterion( new ProductCriterionAdapter( new Criterion\ColorAttribute('color', ['red']) ) ); $redVariants = $this->productService->findVariants($colorQuery); ``` #### Creating variants To create a product variant, use `LocalProductServiceInterface::createProductVariants()`. This method takes the product and an array of [`ProductVariantCreateStruct`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Local-Values-Product-ProductVariantCreateStruct.html) objects as parameters. `ProductVariantCreateStruct` specifies the attribute values and the code for the new variant. ``` $query->setVariantCodes(['DESK-red', 'DESK-blue']); $variantList = $this->productService->findVariants($query); foreach ($variantList->getVariants() as $variant) { $output->writeln($variant->getName()); } ``` ### Product assets You can get assets assigned to a product by using [`AssetServiceInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-AssetServiceInterface.html). Use `AssetServiceInterface` to get a single asset by providing the product object and the assets's ID as parameters: ``` $singleAsset = $this->assetService->getAsset($product, '1'); $output->writeln($singleAsset->getName()); ``` To get all assets assigned to a product, use `AssetServiceInterface::findAssets()`. You can retrieve the tags (corresponding to attribute values) of assets with the `AssetInterface::getTags()` method: ``` $assetCollection = $this->assetService->findAssets($product); foreach ($assetCollection as $asset) { $output->writeln($asset->getIdentifier() . ': ' . $asset->getName()); $tags = $asset->getTags(); foreach ($tags as $tag) { $output->writeln($tag); } } ``` ## Product types To work with product types, use [`ProductTypeServiceInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-ProductTypeServiceInterface.html). ### Creating product types To create a product type, use [`LocalProductTypeServiceInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Local-LocalProductTypeServiceInterface.html). First, create a product type struct with `LocalProductTypeServiceInterface::newProductTypeCreateStruct()`, providing the identifier and main language code: ``` $productTypeCreateStruct = $this->localProductTypeService->newProductTypeCreateStruct( 'digital_product', 'eng-GB' ); ``` You can set names in multiple languages by using `setNames()`: ``` $productTypeCreateStruct->setNames([ 'eng-GB' => 'Digital Product', 'pol-PL' => 'Produkt Cyfrowy', ]); ``` To create a virtual product type (for products that don't require shipping), use `setVirtual()`: ``` $productTypeCreateStruct->setVirtual(true); ``` #### Adding field definitions To add custom field definitions to the product type, use `getContentTypeCreateStruct()` to access the underlying content type struct. For more information about working with content types, see [Adding content types](https://doc.ibexa.co/en/latest/content_management/content_api/managing_content/#adding-content-types). ``` $marketingDescriptionFieldDefinition = $this->contentTypeService->newFieldDefinitionCreateStruct( 'marketing_description', 'ibexa_string' ); $marketingDescriptionFieldDefinition->names = ['eng-GB' => 'Marketing Description']; $marketingDescriptionFieldDefinition->position = 100; $contentTypeCreateStruct->addFieldDefinition($marketingDescriptionFieldDefinition); ``` #### Assigning attributes To assign product attributes to the product type, use `setAssignedAttributesDefinitions()` with an array of [`AssignAttributeDefinitionStruct`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Local-Values-ProductType-AssignAttributeDefinitionStruct.html) objects. First, retrieve the attribute definition by using [`AttributeDefinitionServiceInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-AttributeDefinitionServiceInterface.html): ``` $sizeAttribute = $this->attributeDefinitionService->getAttributeDefinition('size'); ``` Then create the assignment struct with the attribute definition, and set whether it's required and whether it's a discriminator (used for product variants): ``` $attributeAssignment = new AssignAttributeDefinitionStruct( $sizeAttribute, false, false ); $productTypeCreateStruct->setAssignedAttributesDefinitions([$attributeAssignment]); ``` For more information about working with attributes through PHP API, see [Attributes](#attributes). #### Storing new product type Finally, create the product type with `LocalProductTypeServiceInterface::createProductType()`: ``` $newProductType = $this->localProductTypeService->createProductType($productTypeCreateStruct); ``` ### Getting product types Get a product type object by using `ProductTypeServiceInterface::getProductType()`: ``` $productType = $this->productTypeService->getProductType($productTypeIdentifier); ``` You can also get a list of product types with `ProductTypeServiceInterface::findProductTypes()`: ``` $productTypes = $this->productTypeService->findProductTypes(); foreach ($productTypes as $productType) { $output->writeln($productType->getName() . ' with identifier ' . $productType->getIdentifier()); } ``` ## Product availability Product availability is an object which defines whether a product is available, and if so, in what stock. To manage it, use [`ProductAvailabilityServiceInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-ProductAvailabilityServiceInterface.html). To check whether a product is available (with or without stock defined), use `ProductAvailabilityServiceInterface::hasAvailability()`. Get the availability object with `ProductAvailabilityServiceInterface::getAvailability()`. You can then use `ProductAvailabilityServiceInterface::getStock()` to get the stock number for the product: ``` if ($this->productAvailabilityService->hasAvailability($product)) { $availability = $this->productAvailabilityService->getAvailability($product); $output->write($availability->isAvailable() ? 'Available' : 'Unavailable'); $output->writeln(' with stock ' . $availability->getStock()); } ``` To change availability for a product, use `ProductAvailabilityServiceInterface::updateProductAvailability()` with a [`ProductAvailabilityUpdateStruct`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Availability-ProductAvailabilityUpdateStruct.html) and provide it with the product object. The second parameter defines whether product is available, and the third whether its stock is infinite. The fourth parameter is the stock number: ``` $productAvailabilityUpdateStruct = new ProductAvailabilityUpdateStruct($product, true, false, 80); $this->productAvailabilityService->updateProductAvailability($productAvailabilityUpdateStruct); ``` ## Attributes To get information about product attribute groups, use the [`AttributeGroupServiceInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-AttributeGroupServiceInterface.html), or [`LocalAttributeGroupServiceInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Local-LocalAttributeGroupServiceInterface.html) to modify attribute groups. `AttributeGroupServiceInterface::getAttributeGroup()` enables you to get a single attribute group by its identifier. `AttributeGroupServiceInterface::findAttributeGroups()` gets attribute groups, all of them or filtered with an optional [`AttributeGroupQuery`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-AttributeGroup-AttributeGroupQuery.html) object: ``` $attributeGroup = $this->attributeGroupService->getAttributeGroup('dimensions'); $attributeGroups = $this->attributeGroupService->findAttributeGroups(); foreach ($attributeGroups as $attributeGroup) { $output->writeln('Attribute group ' . $attributeGroup->getIdentifier() . ' with name ' . $attributeGroup->getName()); } ``` To create an attribute group, use `LocalAttributeGroupServiceinterface::createAttributeGroup()` and provide it with an [`AttributeGroupCreateStruct`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Local-Values-AttributeGroup-AttributeGroupCreateStruct.html): ``` $attributeGroupCreateStruct = $this->localAttributeGroupService->newAttributeGroupCreateStruct('dimensions'); $attributeGroupCreateStruct->setNames(['eng-GB' => 'Size']); $this->localAttributeGroupService->createAttributeGroup($attributeGroupCreateStruct); ``` To get information about product attributes, use the [`AttributeDefinitionServiceInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-AttributeDefinitionServiceInterface.html), or [`LocalAttributeDefinitionServiceInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Local-LocalAttributeDefinitionServiceInterface.html) to modify attributes. ``` $attribute = $this->attributeDefinitionService->getAttributeDefinition('length'); $output->writeln($attribute->getName()); ``` To create an attribute, use `LocalAttributeGroupServiceinterface::createAttributeDefinition()` and provide it with an [`AttributeDefinitionCreateStruct`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Local-Values-AttributeDefinition-AttributeDefinitionCreateStruct.html): ``` $attributeCreateStruct = $this->localAttributeDefinitionService->newAttributeDefinitionCreateStruct('size'); $attributeCreateStruct->setType($attributeType); $attributeCreateStruct->setName('eng-GB', 'Size'); $attributeCreateStruct->setGroup($attributeGroup); $this->localAttributeDefinitionService->createAttributeDefinition($attributeCreateStruct); ``` # Catalogs You can create multiple catalogs containing subsets of the whole product list. Use them, for example, to build special catalogs for B2B and B2C uses, for retailers and distributors, or for different regions. When creating a catalog, all products are included by default, but you can filter the list by: - price (Solr or Elasticsearch only) - product attributes - product type - product code - availability - product category - the date when the product was created *[Image: List of filters for selecting products for a catalog]* # Catalog API To get information about product catalogs and manage them, use `CatalogServiceInterface`. ## Get catalog To get a single catalog, use `Ibexa\Contracts\ProductCatalog\CatalogServiceInterface::getCatalog()` and provide it with catalog ID, or `CatalogServiceInterface::getCatalogByIdentifier()` and pass the identifier: ``` $catalog = $this->catalogService->getCatalogByIdentifier($catalogIdentifier); $output->writeln($catalog->getName()); ``` ## Get products in catalog To get products from a catalog, request the product query from the catalog object with `Ibexa\Contracts\ProductCatalog\Values\CatalogInterface::getQuery()`. Then, create a new `ProductQuery` based on it and run a product search with `ProductServiceInterface::findProduct()`: ``` $productQuery = new ProductQuery(null, $catalog->getQuery()); $products = $this->productService->findProducts($productQuery); foreach ($products as $product) { $output->writeln($product->getName()); } ``` ## Create catalog To create a catalog, you need to prepare a `CatalogCreateStruct` that contains: identifier, name, description, and Criteria for filtering products. Then, pass this struct to `CatalogServiceInterface::createCatalog()`: ``` $catalogCriterion = new Criterion\LogicalAnd( [ new Criterion\ProductType(['desk']), new Criterion\ProductAvailability(true), ] ); $catalogCreateStruct = new CatalogCreateStruct( $catalogIdentifier, $catalogCriterion, ['eng-GB' => 'Desk promo'], ['eng-GB' => 'Desk promo description'], ); $this->catalogService->createCatalog($catalogCreateStruct); ``` ## Update catalog Use `CatalogServiceInterface::updateCatalog()` to update an existing catalog. You must pass the catalog object and a `CatalogUpdateStruct` to the method. In the following example, you update the catalog to publish it: ``` $catalogUpdateStruct = new CatalogUpdateStruct($catalog->getId()); $catalogUpdateStruct->setTransition(Status::PUBLISH_TRANSITION); $this->catalogService->updateCatalog($catalog, $catalogUpdateStruct); ``` # Enable purchasing products To enable adding product to cart and purchasing from the catalog, the following configuration is required: - at least [one region and one currency for the shop](#region-and-currency) - [VAT rates per region](#vat-rates) and for each product type - at least one [price](https://doc.ibexa.co/en/latest/product_catalog/prices/index.md) for the product - [availability](https://doc.ibexa.co/en/latest/product_catalog/products/#product-availability-and-stock) with positive or infinite stock for the product or product variant > **Note: Configuring products in the UI** > > After you configure the region, currency and VAT rates for regions in settings, the store manager must set up the remaining parameters in the UI, such as, [VAT rates per product type](https://doc.ibexa.co/projects/userguide/en/5.0/pim/create_product_types/#vat), descriptions, attributes, assets, [prices](https://doc.ibexa.co/projects/userguide/en/5.0/pim/manage_prices/), and [availability](https://doc.ibexa.co/projects/userguide/en/5.0/pim/manage_availability_and_stock/) per product. > > For more information, see [User Documentation](https://doc.ibexa.co/projects/userguide/en/5.0/pim/products/#product-completeness). ## Region and currency All currencies available in the system must be enabled in the back office under **Product Catalog** -> **Currencies**. Additionally, you must configure currencies valid for specific SiteAccesses under the `ibexa.system..product_catalog.currencies` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa: system: default: product_catalog: currencies: - EUR - GBP - PLN regions: - germany - uk - poland ``` In the `ibexa_storefront.yaml` file, under the `ibexa.system..product_catalog.regions` configuration key, regions are set with `default` value. Remember to either exclude this element or extend it by [configuring other regions](https://doc.ibexa.co/en/latest/product_catalog/enable_purchasing_products/#configuring-other-regions-and-currencies). ``` ibexa: system: storefront_group: product_catalog: currencies: - EUR - PLN regions: - germany - poland another_storefront_group: product_catalog: currencies: - GBP regions: - uk ``` This example uses the currencies and regions set in the [VAT rates' example below](#vat-rates). ### Configuring other regions and currencies By default, the system always uses the first currency and the first region configured. To implement a different logic, for example a switcher for preferred currencies and regions, you need to subscribe to `Ibexa\Contracts\ProductCatalog\Events\CurrencyResolveEvent` and `Ibexa\Contracts\ProductCatalog\Events\RegionResolveEvent` in your customization. ## VAT rates You set up VAT percentage values corresponding to VAT rates in configuration: ``` ibexa: repositories: default: product_catalog: engine: default regions: germany: # Shorthand VAT configuration format vat_categories: standard: 19 reduced: 7 none: ~ poland: # Current VAT configuration format vat_categories: standard: value: 23 reduced: value: 8 zero: value: 0 none: value: 0 extras: not_applicable: true ``` > **Note: Note** > > The above example presents two acceptable formats of VAT configuration. For each VAT category, setting a value to "null" (`~`) is equal to making the following setting: > > ``` > none: > value: 0 > extras: > not_applicable: true > ``` You can then assign VAT rates that apply to every product type in each of the supported regions. To do it, in the back office, [open the product type for editing](https://doc.ibexa.co/projects/userguide/en/5.0/pim/create_product_types/#vat), and navigate to the **VAT rates** area. *[Image: Assigning VAT rates to a product type]* # Prices The price engine is responsible for calculating prices for products in the [product catalog](https://doc.ibexa.co/en/latest/product_catalog/product_catalog/index.md). ## Custom pricing You can set up basic price rules depending on [customer groups](https://doc.ibexa.co/en/latest/users/customer_groups/index.md), or use [Discounts](https://doc.ibexa.co/en/latest/discounts/discounts/index.md) for more control over the price reduction. Use the first option for basic use cases, for example to globally manage custom prices for your resellers. Each customer group can have a default price discount that applies to all products. With the Discounts feature, you can create time-limited offers that apply only to specified regions, currencies, products, customers, and more. ### Assign prices dynamically You could create a customer group resolver that provides custom price logic, for example, by retrieving user address from the customer profile, and assigning a customer group to the customer based on the address. Such resolver must implement the [`Ibexa\Contracts\ProductCatalog\CustomerGroupResolverInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-CustomerGroupResolverInterface.html) interface. You must then register it as a service with the `ibexa.product_catalog.customer_group.resolver` tag. ## Currency Ibexa DXP ships with a list of available currencies, and you can also add custom currencies. To use currencies in your shop, you need to first enable them in the back office. ## VAT You can [configure VAT rate globally](https://doc.ibexa.co/en/latest/product_catalog/product_catalog_configuration/#vat-rates) (per SiteAccess), or set it individually for each product type and product. # Price API ## Currencies To manage currencies, use [`CurrencyServiceInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-CurrencyServiceInterface.html). To access a currency object by its code, use `CurrencyServiceInterface::getCurrencyByCode`. To access a whole list of currencies, use `CurrencyServiceInterface::findCurrencies`. ``` $currency = $this->currencyService->getCurrencyByCode($currencyCode); $output->writeln('Currency ID: ' . $currency->getId()); $currencies = $this->currencyService->findCurrencies(); foreach ($currencies as $currency) { $output->writeln('Currency ' . $currency->getId() . ' with code ' . $currency->getCode()); } ``` To create a new currency, use `CurrencyServiceInterface::createCurrency()` and provide it with a `CurrencyCreateStruct` with code, number of fractional digits and a flag indicating if the currency is enabled: ``` $currencyCreateStruct = new CurrencyCreateStruct($newCurrencyCode, 2, true); $this->currencyService->createCurrency($currencyCreateStruct); ``` ## Prices To manage prices, use [`ProductPriceServiceInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-ProductPriceServiceInterface.html). To retrieve the price of a product in the currency for the current context, use `Product::getPrice()`: ``` $productPrice = $product->getPrice(); $output->writeln('Price for ' . $product->getName() . ' is ' . $productPrice); ``` To retrieve the price of a product in a specific currency, use `ProductPriceService::getPriceByProductAndCurrency`: ``` $productPrice = $this->productPriceService->getPriceByProductAndCurrency($product, $currency); $output->writeln('Price for ' . $product->getName() . ' in ' . $currencyCode . ' is ' . $productPrice); ``` To get all prices (in different currencies) for a given product, use `ProductPriceServiceInterface::findPricesByProductCode`: ``` $prices = $this->productPriceService->findPricesByProductCode($productCode)->getPrices(); $output->writeln('All prices for ' . $product->getName() . ':'); foreach ($prices as $price) { $output->writeln((string) $price); } ``` To load price definitions that match given criteria, use `ProductPriceServiceInterface::findPrices`: ``` use Ibexa\Contracts\ProductCatalog\Values\Price\PriceQuery; use Ibexa\Contracts\ProductCatalog\Values\Price\Query\Criterion\Currency as CurrencyCriterion; use Ibexa\Contracts\ProductCatalog\Values\Price\Query\Criterion\CustomerGroup; use Ibexa\Contracts\ProductCatalog\Values\Price\Query\Criterion\LogicalOr; // ... $priceCriteria = [ new CurrencyCriterion($this->currencyService->getCurrencyByCode('USD')), new CustomerGroup('customer_group_1'), new Product('ergo_desk'), ]; $priceQuery = new PriceQuery(new LogicalOr(...$priceCriteria)); $prices = $this->productPriceService->findPrices($priceQuery); $output->writeln(sprintf('Found %d prices with provided criteria', $prices->getTotalCount())); ``` You can also use `ProductPriceServiceInterface` to create or modify existing prices. For example, to create a new price for a given currency, use `ProductPriceService::createProductPrice` and provide it with a `ProductPriceCreateStruct` object: ``` $newCurrency = $this->currencyService->getCurrencyByCode($newCurrencyCode); $money = new Money\Money(50000, new Money\Currency($newCurrencyCode)); $priceCreateStruct = new ProductPriceCreateStruct($product, $newCurrency, $money, null, null); $this->productPriceService->createProductPrice($priceCreateStruct); ``` > **Note: Note** > > Prices operate using the [`Money`](https://github.com/moneyphp/money) library. That is why all amounts are provided [in the smallest unit](https://www.moneyphp.org/en/stable/getting-started.html#instantiation). For example, for euro `50000` refers to 50000 cents, equal to 500 euros. ### Resolve prices To display a product price on a product page or in the cart, you must calculate its value based on a base price and the context. Context contains information about any price modifiers that may apply to a specific customer group. To determine the final price, or resolve the price, use the [`PriceResolverInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-PriceResolverInterface.html) service, which takes the following conditions into account: 1. Existence of base price for the product in the specified currency 2. Existence of customer group-related modifiers 3. Existence of applicable [discounts](https://doc.ibexa.co/en/latest/discounts/discounts/index.md) If the base price in the specified currency is missing, the return value is `null`. To resolve a price of a product in the currency for the current context, use either `PriceResolverInterface::resolvePrice()` or `PriceResolverInterface::resolvePrices()`: ``` use Ibexa\Contracts\ProductCatalog\PriceResolverInterface; use Ibexa\Contracts\ProductCatalog\Values\Price\PriceContext; // ... $context = new PriceContext($currency); $price = $this->priceResolver->resolvePrice($product, $context); $output->writeln('Price in ' . $currency->getCode() . ' for ' . $product->getName() . ' is ' . $price); ``` ## VAT To get information about the VAT categories and rates configured in the system, use [`VatServiceInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-VatServiceInterface.html). VAT is configured per region, so you also need to use [`RegionServiceInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-RegionServiceInterface.html) to get the relevant region object. ``` $region = $this->regionService->getRegion('poland'); ``` To get information about all VAT categories configured for the selected region, use `VatServiceInterface::getVatCategories()`: ``` $vatCategories = $this->vatService->getVatCategories($region); foreach ($vatCategories as $category) { $output->writeln($category->getIdentifier() . ': ' . $category->getVatValue()); } ``` To get a single VAT category, use `VatServiceInterface::getVatCategoryByIdentifier()` and provide it with the region object and the identifier of the VAT category: ``` $vatCategory = $this->vatService->getVatCategoryByIdentifier($region, 'reduced'); ``` # Customize product catalog You can customize various areas of the product catalog capabilities to adjust it to the specific requirements of your organization. - [Create custom attribute type](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/product_catalog/create_custom_attribute_type/): Enhance product catalog by creating a custom product attribute type to fit your specific needs. - [Create custom product code generator strategy](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/product_catalog/create_product_code_generator/): A custom product code generator enables you to control how product codes are created. - [Create custom catalog filter](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/product_catalog/create_custom_catalog_filter/): Fine-tune product catalogs by adding a custom catalog filter for selecting products from the PIM. - [Create custom name schema strategy](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/product_catalog/create_custom_name_schema_strategy/): Create custom name schema strategy to generate URL aliases based on attribute values. - [Customize product attribute templates](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/product_catalog/customize_product_attribute_templates/): Customize the Twig templates used to render product attribute values. - [Customize product embed templates](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/product_catalog/customize_product_embed_templates/): Customize the templates used to render products embedded in RichText fields. # Create custom attribute type Besides the [built-in attribute types](https://doc.ibexa.co/en/latest/product_catalog/products/#product-attributes), you can also create custom ones. The example below shows how to add a Percentage attribute type. ## Select attribute type class First, you need to register the type class that the attribute uses: ``` services: app.product_catalog.attribute_type.percent: class: Ibexa\ProductCatalog\Local\Repository\Attribute\AttributeType arguments: $identifier: 'percent' tags: - name: ibexa.product_catalog.attribute_type alias: percent ``` Use the `ibexa.product_catalog.attribute_type` tag to indicate the use as a product attribute type. The custom attribute type has the identifier `percent`. ## Create value form mapper A form mapper maps the data entered in an editing form into an attribute value. The form mapper must implement `Ibexa\Contracts\ProductCatalog\Local\Attribute\ValueFormMapperInterface`. In this example, you can use the Symfony's built-in `PercentType` class (line 40). ``` getAttributeDefinition(); $options = [ 'disabled' => $context['translation_mode'] ?? false, 'label' => $definition->getName(), 'block_prefix' => 'percentage_attribute_value', 'required' => $assignment->isRequired(), 'constraints' => [ new AttributeValue([ 'definition' => $definition, ]), ], ]; if ($assignment->isRequired()) { $options['constraints'][] = new Assert\NotBlank(); } $builder->add($name, PercentType::class, $options); } } ``` The `options` array contains additional options for the form, including options resulting from the selected form type. Register the form mapper as a service and tag it with `ibexa.product_catalog.attribute.form_mapper.value`: ``` App\Attribute\Percent\Form\PercentValueFormMapper: tags: - name: ibexa.product_catalog.attribute.form_mapper.value type: percent ``` ## Create value formatter A value formatter prepares the attribute value for rendering in the proper format. In this example, you can use the `NumberFormatter` to ensure the number is rendered in the percentage form (line 22). ``` getValue(); if ($value === null) { return null; } $formatter = $parameters['formatter'] ?? null; if ($formatter === null) { $formatter = new NumberFormatter('', NumberFormatter::PERCENT); } return $formatter->format($value); } } ``` Register the value formatter as a service and tag it with `ibexa.product_catalog.attribute.formatter.value`: ``` App\Attribute\Percent\PercentValueFormatter: tags: - name: ibexa.product_catalog.attribute.formatter.value type: percent ``` ## Add attribute options You can also add options specific for the attribute type that the user selects when creating an attribute. In this example, you can set the minimum and maximum allowed percentage. ### Options type First, create `PercentAttributeOptionsType` that defines two options, `min` and `max`. Both those options need to be of `PercentType`. ``` add('min', PercentType::class, [ 'disabled' => $options['translation_mode'], 'label' => 'Minimum Value', 'required' => false, ]); $builder->add('max', PercentType::class, [ 'disabled' => $options['translation_mode'], 'label' => 'Maximum Value', 'required' => false, ]); } public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'translation_mode' => false, ]); $resolver->setAllowedTypes('translation_mode', 'bool'); } } ``` ### Options form mapper Next, create a `PercentOptionsFormMapper` that maps the information that the user inputs in the form into attribute definition. ``` add($name, PercentAttributeOptionsType::class, [ 'constraints' => [ new AttributeDefinitionOptions(['type' => $context['type']]), ], 'translation_mode' => $context['translation_mode'], ]); } } ``` Register the options form mapper as a service and tag it with `ibexa.product_catalog.attribute.form_mapper.options`: ``` app.product_catalog.attribute.percent.form_mapper.options: class: App\Attribute\Percent\PercentOptionsFormMapper tags: - name: ibexa.product_catalog.attribute.form_mapper.options type: percent ``` ### Options validator Create a `PercentOptionsValidator` that implements `Ibexa\Contracts\ProductCatalog\Local\Attribute\OptionsValidatorInterface`. It validates the options that the user sets while creating the attribute definition. In this example, the validator verifies whether the minimum percentage is lower than the maximum. ``` get('min'); $max = $options->get('max'); if ($min !== null && $max !== null && $min > $max) { return [ new OptionsValidatorError('[max]', 'Maximum value should be greater than minimum value'), ]; } return []; } } ``` Register the options validator as a service and tag it with `ibexa.product_catalog.attribute.validator.options`: ``` app.product_catalog.attribute.options_validator.percent: class: App\Attribute\Percent\PercentOptionsValidator tags: - name: ibexa.product_catalog.attribute.validator.options type: percent ``` ### Value validator Finally, make sure the data provided by the user is validated. To do that, create `PercentValueValidator` that checks the values against `min` and `max` and dispatches an error when needed. ``` getOptions(); $min = $options->get('min'); if ($min !== null && $value < $min) { $errors[] = new ValueValidationError(null, 'Percentage should be greater or equal to %min%', [ '%min%' => $min, ]); } $max = $options->get('max'); if ($max !== null && $value > $max) { $errors[] = new ValueValidationError(null, 'Percentage should be lesser or equal to %max%', [ '%max%' => $max, ]); } return $errors; } } ``` Register the validator as a service and tag it with `ibexa.product_catalog.attribute.validator.value`: ``` app.product_catalog.attribute.value_validator.percent: class: App\Attribute\Percent\PercentValueValidator tags: - name: ibexa.product_catalog.attribute.validator.value type: percent ``` ## Storage To ensure that values of the new attributes are stored correctly, you need to provide a storage converter and storage definition services. ### Database schema design The values are going to be stored within a table named `app_product_specification_attribute_percent`, in a column named `value`. **MySQL** ``` CREATE TABLE app_product_specification_attribute_percent ( id INT NOT NULL, value DOUBLE PRECISION DEFAULT NULL, INDEX app_product_specification_attribute_percent_value_idx (value), PRIMARY KEY (id) ) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_520_ci` ENGINE = InnoDB; ``` **PostgreSQL** ``` CREATE TABLE app_product_specification_attribute_percent (id INT NOT NULL, value DOUBLE PRECISION DEFAULT NULL, PRIMARY KEY(id)); CREATE INDEX app_product_specification_attribute_percent_value_idx ON app_product_specification_attribute_percent (value); ALTER TABLE app_product_specification_attribute_percent ADD CONSTRAINT app_product_specification_attribute_percent_fk FOREIGN KEY (id) REFERENCES ibexa_product_specification_attribute (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE; ``` ### Storage converter Start by creating a `PercentStorageConverter` class, which implements `Ibexa\Contracts\ProductCatalog\Local\Attribute\StorageConverterInterface`. This converter is responsible for converting database results into an attribute type instance: ``` $value, ]; } } ``` Register the converter as a service and tag it with `ibexa.product_catalog.attribute.storage_converter`: ``` App\Attribute\Percent\Storage\PercentStorageConverter: tags: - { name: 'ibexa.product_catalog.attribute.storage_converter', type: 'percent' } ``` ### Storage definition You can either create a new storage definition or use an existing one. To create a new storage definition, prepare a `PercentStorageDefinition` class, which implements `Ibexa\Contracts\ProductCatalog\Local\Attribute\StorageDefinitionInterface`. ``` Types::FLOAT, ]; } public function getTableName(): string { return 'app_product_specification_attribute_percent'; } } ``` Register the storage definition as a service and tag it with `ibexa.product_catalog.attribute.storage_definition`: ``` App\Attribute\Percent\Storage\PercentStorageDefinition: tags: - { name: 'ibexa.product_catalog.attribute.storage_definition', type: 'percent' } ``` If you prefer to use an existing storage definition, you need to create a Storage Definition Tag CompilerPass `src/DependencyInjection/AddFloatStorageDefinitionTag.php`: ``` getDefinition(StorageDefinition::class) ->addTag('ibexa.product_catalog.attribute.storage_definition', ['type' => 'percent']); } } ``` Add the CompilerPass to the container. Do it in a `src/Kernel.php` file or in your Bundle class: ``` addCompilerPass(new AddFloatStorageDefinitionTag()); } } ``` ## Use new attribute type In the back office you can now add a new Percent attribute to your product type and create a product with it. *[Image: Creating a product with a custom Percent attribute]* # Create custom product code generator strategy Product code generator strategies control what product variant codes are generated. Besides the [built-in](https://doc.ibexa.co/en/latest/product_catalog/product_catalog_configuration/#code-generation-strategy) strategies, you can create your own ones. A code generator strategy must implement `Ibexa\Contracts\ProductCatalog\Local\CodeGenerator\CodeGeneratorInterface`. The following example shows how to add a generator strategy that creates code, based on the product base code and an incremental index number. First, create the generator strategy class: ``` hasBaseProduct()) { throw new InvalidArgumentException('$context', 'missing base product'); } if (!$context->hasIndex()) { throw new InvalidArgumentException('$context', 'missing index'); } return $context->getBaseProduct()->getCode() . 'v' . $context->getIndex(); } } ``` This generator uses the provided context to get product information (in this case the code of the base product) and the incremental number. Then, register the strategy generator as a service and tag it with `ibexa.product_catalog.code_generator`: ``` services: App\CodeGenerator\Strategy\CustomIncrementalCodeGenerator: tags: - { name: 'ibexa.product_catalog.code_generator', type: 'custom_incremental' } ``` Use the defined `type` in [catalog configuration](https://doc.ibexa.co/en/latest/product_catalog/product_catalog_configuration/#code-generation-strategy) to apply codes generated by this strategy to new product variants. # Create custom catalog filter Catalog filters let you narrow down the products from the PIM that are available in the given [catalog](https://doc.ibexa.co/en/latest/product_catalog/catalogs/index.md). Besides the built-in catalog filters, you can also create custom ones. The following example shows how to create a filter that selects products with the entered name. ## Create filter class To create a custom catalog filter, first you need to create a filter class in `App\CatalogFilter\ProductNameFilter`. ``` add( $filterDefinition->getIdentifier(), TagifyType::class, [ 'label' => 'Product name', 'block_prefix' => 'catalog_criteria_product_name', 'translation_domain' => 'product_catalog', ] ); $builder->get($filterDefinition->getIdentifier()) ->addModelTransformer( new DataTransformer\ProductNameCriterionTransformer() ); } public function supports(FilterDefinitionInterface $filterDefinition): bool { return $filterDefinition instanceof ProductNameFilter; } } ``` The filter can use the built-in `ProductName` Criterion, but you still need a data transformer for the data entered when editing the catalog (line 35). Before you add a data transformer, register the required services. ## Register services Register the filter and its mapper as services. Tag the filter with `ibexa.product_catalog.catalog_filter` and the form mapper with `ibexa.product_catalog.catalog_filter.form_mapper`: ``` services: App\CatalogFilter\ProductNameFilter: tags: - name: ibexa.product_catalog.catalog_filter alias: product_name App\CatalogFilter\ProductNameFilterFormMapper: tags: - name: ibexa.product_catalog.catalog_filter.form_mapper ``` ## Create data transformer Now, create `ProductNameCriterionTransformer` in `src/CatalogFilter/DataTransformer`: ``` getName(); } public function reverseTransform($value): ?ProductName { if ($value === null) { return null; } if (!is_string($value)) { throw new TransformationFailedException('Invalid data, expected a string value'); } return new ProductName($value); } } ``` ## Provide templates Now, provide the templates for the catalog editing view in the back office. You need two templates: one for the filter form, and one for the filter badge in the product list. First, add a `form_field_override.html.twig` template to `templates/themes/admin/product_catalog`: ``` {% extends '@ibexadesign/product_catalog/form_fields.html.twig' %} {%- block catalog_criteria_product_name_row -%} {{- block('catalog_taggify_panel') -}} {%- endblock -%} ``` Here, you use the same built-in template that is used for example for the product code filter. It's placed in a template block corresponding to your custom filter, `catalog_criteria_product_name_values`. To ensure the template is used as a back office form theme, add the following configuration: ``` twig: form_themes: - '@ibexadesign/product_catalog/form_field_override.html.twig' ``` Next, add a template that handles the display of the filter badge on the list of the currently filtered products. Add `catalog_filters_blocks.html.twig` to `templates/themes/admin/product_catalog`: ``` {% block catalog_criteria_product_name_values %} {% include '@ibexadesign/product_catalog/catalog/edit/list_filter_taggify.html.twig' with { criteria } %} {% endblock %} ``` To ensure this template is used to render the catalog filter form, add the following configuration: ``` ibexa: system: default: product_catalog: catalogs: filter_preview_templates: - { template: "@ibexadesign/product_catalog/catalog_filters_blocks.html.twig", priority: 10 } ``` ## Check results Finally, you can check the results. Go to **Product catalog** -> **Catalogs** and create a new catalog. From the filter list, select **Product name**, type the name of an existing product and click **Save**. *[Image: Custom Product Name catalog filter]* # Create custom name schema strategy You can create custom name schema strategy to generate URL aliases based on attribute values. Make sure the attributes are configured correctly. Each attribute that you want to include in the URL alias must have a name schema strategy. ## Create converting class Start by creating a `PercentNameSchemaStrategy` class, which implements `\Ibexa\Contracts\ProductCatalog\NameSchema\NameSchemaStrategyInterface`. This class is responsible for converting attribute values into a string of URL parameters: ``` getType()->getIdentifier() === 'percent'; } } ``` ## Register strategy Next, you need to register the strategy in the dependency injection container: ``` services: _defaults: public: false autowire: true autoconfigure: true App\Attribute\Percent\PercentNameSchemaStrategy: tags: - { name: ibexa.product_catalog.naming_schema_strategy } ``` This ensures that the custom name schema strategy is available for use in generating URL aliases based on attribute values. # Customize product attribute templates The `ibexa_format_product_attribute` Twig filter renders a product attribute value by using a configurable list of Twig templates. Each template contains Twig blocks that control how specific [attribute types](https://doc.ibexa.co/en/latest/product_catalog/products/#product-attributes) are displayed. You can customize this rendering by: - adding your own template [to the configuration](https://doc.ibexa.co/en/latest/product_catalog/product_catalog_configuration/#attribute-rendering-templates). - injecting a template by subscribing to the [`ProductAttributeRenderEvent`](https://doc.ibexa.co/en/latest/api/event_reference/product_catalog_events/#attribute-rendering) event ## Template blocks Each template can define the following blocks: | Block | Used for | | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `_attribute` | Rendering an attribute of a specific type. Replace `` with the attribute type identifier, for example `color_attribute` or `integer_attribute`. | | `generic_attribute` | Fallback block used when no type-specific block is found. | For a list of available attributes, see [product attributes](https://doc.ibexa.co/en/latest/product_catalog/products/#product-attributes). When rendering an attribute, the system iterates through the configured templates in order and uses the first matching type-specific block it finds. If none is found, it falls back to the first `generic_attribute` block available. ### Quable attribute types When using [Quable PIM](https://doc.ibexa.co/en/latest/product_catalog/quable/quable/index.md), use the following identifiers to override the templates for [Quable's attribute types](https://docs.quable.com/v5-EN/docs/objects-and-attributes#attribute-types): | Attribute name | Identifier | | ------------------------------------ | ------------------ | | Simple text not localized | `unlocalized_text` | | Simple text localized | `localized_text` | | Text area localized | `multiline_text` | | HTML code | `html_text` | | JSON code | `json_text` | | Integer | `integer` | | Decimal | `decimal` | | Date | `date` | | Time | `time` | | Checkbox | `switch` | | Simple select of predefined values | `simple_select` | | Select of multiple predefined values | `multi_select` | | Calculated | `calculated` | ### Template variables The following variables are available in attribute template blocks: | Variable | Type | Description | | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | | `attribute` | [`AttributeInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-AttributeInterface.html) | The attribute object being rendered. | | `value` | `?string` | The pre-formatted attribute value, produced by the value formatter. | | `parameters` | `array` | Optional rendering parameters passed to the filter or modified by an event subscriber. | ## Create custom attribute template Create a Twig template and define the blocks for the attribute types you want to customize: - To customize a specific attribute type, define a block named `_attribute`. - To handle all remaining types, define a `generic_attribute` block. The following example adds a custom template for an `integer` attribute type: ``` {# templates/product/attributes/integer_attribute.html.twig #} {% block integer_attribute %} Integer value: {{ value }} {% endblock %} ``` Then, [configure the product catalog](https://doc.ibexa.co/en/latest/product_catalog/product_catalog_configuration/#attribute-rendering-templates) to use it: ``` ibexa_product_catalog: templates: attributes: - 'templates/product/attributes/integer_attribute.html.twig' ``` ## Inject templates at runtime You can inject additional templates by listening to the [`ProductAttributeRenderEvent`](https://doc.ibexa.co/en/latest/api/event_reference/product_catalog_events/#attribute-rendering) event. Use this option when you want to add templates conditionally, for example based on the active catalog engine or region. ``` addTemplateBefore( 'templates/product/attributes/integer_attribute.html.twig', '@ibexadesign/product_catalog/product/attributes/attribute_blocks.html.twig', ); } } ``` # Customize product embed templates When a product is [embedded in a RichText field](https://doc.ibexa.co/en/latest/product_catalog/products/#embed-products-in-content), it is rendered by using a Twig template. You can override the default templates to customize the appearance of embedded products. ## Embed types Six embed types exist in the system, each with its own template: | Embed type | Description | | -------------------------- | ----------------------------------------------------------------------- | | `product` | Block-level embed when the product is found and the user has access. | | `product_inline` | Inline embed when the product is found and the user has access. | | `product_denied` | Block-level embed when the user has no access to view the product data. | | `product_inline_denied` | Inline embed when the user has no access to view the product data. | | `product_not_found` | Block-level embed when the product code cannot be found. | | `product_inline_not_found` | Inline embed when the product code cannot be found. | ## Template variables The following variables are available in the embed templates: | Variable | Available in | Description | | ------------- | ------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `product` | `product`, `product_inline` | A [`ProductInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-ProductInterface.html) object. | | `productCode` | `product_denied`, `product_inline_denied`, `product_not_found`, `product_inline_not_found` | The product code string, used to identify the product that could not be loaded. | | `embedParams` | All block types | Optional parameters set by the online editor, for example `align` or `class` properties | ## Override template The default templates are located in `vendor/ibexa/product-catalog/src/bundle/Resources/views/themes/standard/product_catalog/richtext/embed/`. To override a template, create a file with the same name in your [theme directory](https://doc.ibexa.co/en/latest/templating/design_engine/design_engine/index.md). For example, to override the block embed template for the `standard` theme, create a file in `templates/themes/standard/product_catalog/richtext/embed/product.html.twig` A minimal product embed template looks as follows: ```
    {{ product.name }}
    ``` And a minimal inline embed template (`product_inline.html.twig`): ``` {{ product.name }} ``` ## Configure template paths In addition to overriding the templates with the [design engine](https://doc.ibexa.co/en/latest/templating/design_engine/design_engine/index.md), you can explicitly set the template path for any embed type in your [SiteAccess configuration](https://doc.ibexa.co/en/latest/multisite/multisite_configuration/index.md): ``` ibexa: system: : fieldtypes: ibexa_richtext: embed: product: template: '@ibexadesign/product_catalog/richtext/embed/product.html.twig' product_inline: template: '@ibexadesign/product_catalog/richtext/embed/product_inline.html.twig' product_denied: template: '@ibexadesign/product_catalog/richtext/embed/product_denied.html.twig' product_inline_denied: template: '@ibexadesign/product_catalog/richtext/embed/product_inline_denied.html.twig' product_not_found: template: '@ibexadesign/product_catalog/richtext/embed/product_not_found.html.twig' product_inline_not_found: template: '@ibexadesign/product_catalog/richtext/embed/product_inline_not_found.html.twig' ``` Replace `` with the name of your SiteAccess or SiteAccess group (for example, `default`). # Add Remote PIM support Ibexa DXP provides flexible product catalog infrastructure that works with external Product Information Management (PIM) systems. For advanced product data management without custom development, you can use the readily available [Quable PIM integration](https://doc.ibexa.co/en/latest/product_catalog/quable/quable/index.md) with Ibexa DXP. To implement [Remote PIM support](https://doc.ibexa.co/en/latest/product_catalog/product_catalog_guide/#remote-pim-support) for a custom integration, you can build upon a foundation provided by Ibexa. While doing so, you must implement services that process data coming from the remote PIM. Before you create your own solution, you can [install an example package](#install-remote-pim-example-package) and modify it to connect to your external data source. ## Implement services To connect to your remote PIM, provide your implementation of the following services that process product data: - AssetService, used to get assets assigned to a product. - AttributeDefinitionService, used to get information about product attributes. - AttributeGroupService, used to get information about product attribute groups. - ProductService, used to get product information. - ProductTypeService, used to work with product types. ## Switch to the new product catalog engine To inform the application that the product catalog engine has been replaced by an external one, in `config/packages/ibexa_product_catalog.yaml`, set the new product catalog engine, for example: ``` ibexa_product_catalog: engines: : type: options: root_location_remote_id: ibexa_product_catalog_root ``` Then configure the application to use the engine defined above as the default product data repository: ``` ibexa: repositories: : # ... product_catalog: engine: ``` > **Note: Enabling the remote PIM support** > > By default, the `ibexa.repositories..product_catalog.engine.type` key is set to `local`, which informs Ibexa DXP that the built-in product catalog capabilities are used. By changing this setting and the `ibexa.repositories..product_catalog.engine` setting from `default` to your custom value, you inform Ibexa DXP that you're using a remote PIM. ## Install Remote PIM example package The example implementation provides services that take over the role of services provided by the product catalog package. You can modify them to suit your needs. Install the `ibexa/example-in-memory-product-catalog` package: ``` composer config repositories.remote-pim vcs https://github.com/ibexa/example-in-memory-product-catalog composer require ibexa/example-in-memory-product-catalog: ``` # Commerce # Commerce Editions: Commerce The commerce component of Ibexa DXP covers various areas of managing an e-commerce presence: from configuring payment and shipping methods, through processing a transaction (listing available products, adding products to a cart, processing checkout, and sending confirmation), all the way to order management. - [Cart](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/commerce/cart/cart/): The cart component covers adding items to the shopping cart, and previewing or modifying the cart information. - [Shopping list](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/commerce/shopping_list/shopping_list/): Shopping list allows users to save potential purchases, recurring product sets, and other items for future use in the cart. - [Checkout](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/commerce/checkout/checkout/): The checkout component covers providing shipping and billing addresses, and selecting payment and shipping methods. - [Order management](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/commerce/order_management/order_management/): The order management component covers creating orders and managing their lifecycle. - [Payment](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/commerce/payment/payment/): The payment component covers defining and managing payment methods, together with managing payments and their lifecycle. - [Shipping](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/commerce/shipping_management/shipping_management/): The shipping component covers defining and managing shipping methods, together with managing shipments and their lifecycle. - [Storefront](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/commerce/storefront/storefront/): Storefront covers actions related to the purchase process. - [Transactional emails](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/commerce/transactional_emails/transactional_emails/): With transactional emails you can notify end users about changes in the status of user registration, password recovery, orders, payments, shipments, and more. ## Configure - [Configure checkout](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/commerce/checkout/configure_checkout/): Configure checkout, modify the default checkout workflow. - [Configure order processing](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/commerce/order_management/configure_order_management/): Configure order processing, modify the default workflow. - [Configure payment](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/commerce/payment/configure_payment/): Configure payments, modify the default payment processing workflow. - [Configure shipping](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/commerce/shipping_management/configure_shipment/): Configure shipping, modify the default shipment workflow. - [Configure Storefront](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/commerce/storefront/configure_storefront/): Configure Storefront, including catalogs used, customer groups and user accounts. ## Extend - [Customize checkout](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/commerce/checkout/customize_checkout/): Customize the existing checkout functionality to support additional functions. - [Extend Payment](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/commerce/payment/extend_payment/): Extend Payment with custom payment method types. - [Extend shipping](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/commerce/shipping_management/extend_shipping/): Extend Shipping with custom shipping method type and other extra features. - [Extend Storefront](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/commerce/storefront/extend_storefront/): Extend Storefront with new menus. - [Customize transactional emails](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/commerce/transactional_emails/extend_transactional_emails/): Customize transactional emails to meet your specific business requirements. ## Explore Commerce API - [Cart API](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/commerce/cart/cart_api/): Use PHP API and REST API to work with carts in Commerce, manage cart entries, or validate products. - [Checkout API](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/commerce/checkout/checkout_api/): Use PHP API to work with checkouts in Commerce. - [Order management API](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/commerce/order_management/order_management_api/): Use PHP API and REST API to manage orders in Commerce. - [Payment API](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/commerce/payment/payment_api/): Use PHP API to manage payments in Commerce. You can create, update and delete payments. - [Payment method API](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/commerce/payment/payment_method_api/): Use PHP API and REST API to manage payment methods in Commerce. You can create, modify and delete payment methods. - [Shipping method API](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/commerce/shipping_management/shipping_method_api/): Use PHP API to manage shipping methods in Commerce. Create and update shipping methods, delete shipping methods and their translations. - [Shipment API](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/commerce/shipping_management/shipment_api/): Use PHP API to manage shipments in Commerce. Create, update and delete shipments. # Cart Editions: Commerce The cart component is a foundation of the Commerce offering delivered as part of Ibexa Commerce. It covers actions related to the creation and handling of a list of products that the buyer intends to purchase. The component exposes the following: - [PHP API](https://doc.ibexa.co/en/latest/commerce/cart/cart_api/index.md) that allows for managing carts and cart entries, or checking cart validity - [REST API](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Cart) that helps get cart and products information over HTTP - [Twig functions](https://doc.ibexa.co/en/latest/templating/twig_function_reference/cart_twig_functions/index.md) that enable checking whether product can be added to cart and formatting the price There is no specific configuration related to the cart component. All configuration is done at the checkout and storefront level. Cart constructor takes the following arguments: - `userId` - by default, read from the header's meta element with `name="UserId"`, where variable type must be integer - `currencyCode` - by default, read from the header's meta element with `name="ActiveCurrencyCode"` - `lang` - by default, read from the document element's `lang` attribute ## Cart data handling Cart data is handled by two storages, depending on whether the buyer is anonymous or has been authenticated. Information that relates to anonymous users is stored in the PHP session, while registered user data is stored in a database. By default, anonymous users can add items to cart, but to display the cart view, they have to log in and transition into an authenticated user. > **Note: Note** > > For information about roles and permissions that control access to the cart, see [Permission use cases](https://doc.ibexa.co/en/latest/permissions/permission_use_cases/#commerce). ### Cart data merging When a buyer browses the storefront anonymously and fills the cart with items, anonymous cart data is stored in the PHP session storage. Then, when an anonymous user logs into the storefront, cart data from the PHP session storage is persisted and merged with any cart information that might already exist in the database for this authenticated user. If no previous cart data exists, a new cart is created. ### Cart data validation When a buyer tries to add products to the cart, increase cart item quantity, or proceed to checkout, the cart component performs cart item validation and checks whether: - the product is available - the requested quantity of product is available - the product is available at a price in the currency selected for the cart ## Front-end perspective From the front-end perspective, the cart consists of a main `Cart` object and several widgets. `Cart` is a standalone JavaScript object that manages cart data and has no user interface, while widgets consist of JavaScript code and accompanying Twig templates. ### Cart service object The `Cart` service object stores cart entry data and a cart summary, which contains additional entry data, like, for example, formatted gross price or validation errors. The object exposes several methods, which you can use to get and modify cart entries. Only one instance of a `Cart` service object can be created. ### Cart events When cart data is changed or loaded, the `ibexa-cart:cart-data-changed` event is triggered on `body`. The reference to the Cart is sent in the event's `detail`. ``` document.body.addEventListener( 'ibexa-cart:cart-data-changed', ({ detail: { cart } }) => { refreshMyWidget(cart); }, false, ); ``` ### Cart service The Cart package provides `Ibexa\Contracts\Cart\CartServiceInterface` Symfony service, which is the entrypoint for calling the [backend API](https://doc.ibexa.co/en/latest/commerce/cart/cart_api/index.md). You can import the service with the following code: ``` import * as cartService from '@ibexa-cart/src/bundle/Resources/public/js/service/cart'; ``` Use the service in your code as follows: ``` cartService.deleteCartEntry(cartIdentifier, entryIdentifier); ``` Every cart service function returns a `Promise` object with a parsed response. When the request isn't `OK`, it can throw an error with the response `statusText`. - `loadUserCarts(ownerId)` - loads 10 user carts - `loadCartSummary(cartIdentifier)` - load cart summary data - `createCart(currencyCode)` - creates a new cart - `deleteCart(cartIdentifier)` - deletes the cart - `createCartEntry(cartIdentifier, productCode, quantity)` - creates a new cart entry for the specified product - `updateProductQuantity(cartIdentifier, entryIdentifier, quantity)` - updates product quantity to a new value - `deleteCartEntry(cartIdentifier, entryIdentifier)` - deletes cart entry - `emptyCart(cartIdentifier)` - empties cart by removing all entries, returns Promise To import and initialize cart (without extending it or passing any options), add the following: ``` import Cart from '@ibexa-cart/src/bundle/Resources/public/js/component/cart'; new Cart(); ``` ### Change request before sending Before every request is sent by `cartService`, the `ibexa-cart:prepare-request` event is triggered on `document`, so you can change request object by assigning a new one to `detail.request`: ``` document.addEventListener( 'ibexa-cart:prepare-request', (event) => { event.detail.request = modifiedRequest; }, false, ); ``` ### Widgets Ibexa DXP comes with a number of components that are interfaces to various functionalities exposed by the Cart. For a list of default components available in the storefront, see [Default UI components](https://doc.ibexa.co/en/latest/commerce/storefront/storefront/#default-ui-components). To customize your store, you can override the Twig templates and extend their logic. For more information, see [Customize storefront layout](https://doc.ibexa.co/en/latest/templating/layout/customize_storefront_layout/index.md). # Cart API Editions: Commerce > **Tip: Cart REST API** > > To learn how to manage carts with the REST API, see the [REST API reference](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Cart). To get carts and work with them, use the `Ibexa\Contracts\Cart\CartServiceInterface` interface. `CartService` uses two storage methods and handles switching between storages: - carts of registered users use database-based storage - anonymous user carts are stored in the PHP session From the developer's perspective, carts and entries are referenced with a UUID identifier. ## Get single cart by identifier To access a single cart, use the `CartServiceInterface::getCart` method: ``` $cart = $this->cartService->getCart('1844450e-61da-4814-8d82-9301a3df0054'); $output->writeln($cart->getName()); ``` ## Get multiple carts To fetch multiple carts, use the `CartServiceInterface::findCarts` method. It follows the same search Query pattern as other APIs: ``` use Ibexa\Contracts\Cart\Value\CartQuery; // ... $cartQuery = new CartQuery(); $cartQuery->setOwnerId(14); // carts created by User ID: 14 $cartQuery->setLimit(20); // fetch 20 carts $cartsList = $this->cartService->findCarts($cartQuery); $cartsList->getCarts(); // array of CartInterface objects $cartsList->getTotalCount(); // number of matching carts regardless of the limit ``` ## Create cart To create a cart, use the `CartServiceInterface::createCart` method and provide it with `Ibexa\Contracts\Cart\Value\CartCreateStruct` that contains metadata (name, currency, owner): ``` use Ibexa\Contracts\Cart\Value\CartCreateStruct; // ... $cartCreateStruct = new CartCreateStruct( 'Default cart', $currency // Ibexa\Contracts\ProductCatalog\Values\CurrencyInterface ); $cart = $this->cartService->createCart($cartCreateStruct); $output->writeln($cart->getName()); // prints 'Default cart' to the console ``` ## Update cart metadata You can update cart metadata after the cart is created. You could do it to support a scenario when, for example, the user changes a currency and the cart should recalculate all item prices to a new currency. To update cart metadata, use the `CartServiceInterface::updateCartMetadata` method: ``` use Ibexa\Contracts\Cart\Value\CartMetadataUpdateStruct; // ... $cartUpdateMetadataStruct = new CartMetadataUpdateStruct(); $cartUpdateMetadataStruct->setName('New name'); $cartUpdateMetadataStruct->setCurrency($newCurrency); $updatedCart = $this->cartService->updateCartMetadata($cart, $cartUpdateMetadataStruct); $output->writeln($updatedCart->getName()); // prints 'New name' to the console ``` You can also use this method to change cart ownership: ``` use Ibexa\Contracts\Cart\Value\CartMetadataUpdateStruct; // ... $updateMetadataStruct = new CartMetadataUpdateStruct(); $updateMetadataStruct->setOwner($userService->loadUserByLogin('user')); $cart = $cartService->updateCartMetadata($cart, $updateMetadataStruct); ``` ## Delete cart To delete a cart permanently, use the `CartServiceInterface::deleteCart` method and pass the `CartInterface` object: ``` $cart = $this->cartService->getCart('1844450e-61da-4814-8d82-9301a3df0054'); $this->cartService->deleteCart($cart); ``` ## Empty cart To remove all products from the cart in a single operation, use the `CartServiceInterface::emptyCart` method: ``` $cart = $this->cartService->getCart('1844450e-61da-4814-8d82-9301a3df0054'); $this->cartService->emptyCart($cart); ``` ## Check cart validity Items in cart can become invalid, for example, when item price is unavailable in cart currency, or the product is no longer available. To prevent checking out a cart with invalid items, check cart validity first. To validate the cart, use the `CartServiceInterface::validateCart` method. Validation is done with help from the `symfony/validator` component, and the method returns a `Symfony\Component\Validator\ConstraintViolationListInterface` object. ``` $cart = $this->cartService->getCart('1844450e-61da-4814-8d82-9301a3df0054'); $violationList = $this->cartService->validateCart($cart); // Symfony\Component\Validator\ConstraintViolationListInterface ``` ## Add entry to cart To add entries (products) to the cart, create an `Ibexa\Contracts\Cart\Value\EntryAddStruct`, where you specify the requested quantity of the product. Then pass it to the `CartServiceInterface::addEntry` method: ``` use Ibexa\Contracts\Cart\Value\EntryAddStruct; // ... $cart = $this->cartService->getCart('1844450e-61da-4814-8d82-9301a3df0054'); $entryAddStruct = new EntryAddStruct($product); $entryAddStruct->setQuantity(10); $cart = $this->cartService->addEntry($cart, $entryAddStruct); $entry = $cart->getEntries()->first(); $output->writeln($entry->getProduct()->getName()); // prints product name to the console ``` ## Remove entry from cart To remove an entry from the cart, use the `CartServiceInterface::removeEntry` method. ``` $cart = $this->cartService->getCart('1844450e-61da-4814-8d82-9301a3df0054'); $entry = $cart->getEntries()->first(); $cart = $this->cartService->removeEntry($cart, $entry); // updated Cart object ``` ## Update entry metadata Entries have their own metadata, for example, quantity. To change entry metadata, use the `CartServiceInterface::updateEntry` method and provide it with `Ibexa\Contracts\Cart\Value\EntryUpdateStruct`. ``` use Ibexa\Contracts\Cart\Value\EntryUpdateStruct; // ... $cart = $this->cartService->getCart('1844450e-61da-4814-8d82-9301a3df0054'); $entry = $cart->getEntries()->first(); $entryUpdateStruct = new EntryUpdateStruct(5); $entryUpdateStruct->setQuantity(10); $cart = $this->cartService->updateEntry( $cart, $entry, $entryUpdateStruct ); // updated Cart object ``` ## Adding context data Context data is an extra information that you can attach to the cart or cart entries to provide additional details or attributes related to the shopping experience. It can include any relevant information that you want to associate with a particular cart or cart entry, for example, coupon codes, custom products attributes, or user preferences. ### Adding context data to cart To add context data to a cart, follow this example: ``` $createStruct = new CartCreateStruct(...); $createStruct->setContext(new ArrayMap([ 'coupon_code' => 'X1MF7699', ])); $cart = $cartService->createCart($createStruct); ``` In the above example, you create a cart with the `CartCreateStruct` method, and set the context data with `setContext`. You also add "X1MF7699" coupon code as context data to the cart. ### Adding context data to cart entry To attach context data to a cart entry, proceed as follows: ``` $entryAddStruct = new EntryAddStruct(...); $entryAddStruct->setContext(new ArrayMap([ 'tshirt_text' => 'EqEqEqEq', ])); $cartService->addEntry($cart, $entryAddStruct); ``` In the above example, you create a cart entry by using the `EntryAddStruct` method. The `setContext` method allows you to attach context data to the cart entry. In this case, you attach a "tshirt_text" attribute to the cart entry, which might represent custom text for a T-shirt. ## Merge carts To combine the contents of multiple shopping carts into a target cart, use the `CartServiceInterface::mergeCarts` method. This operation is helpful when you want to consolidate items from a reorder cart and a current cart into a single order. ``` // Get the order with items that should be reordered $orderIdentifier = '2e897b31-0d7a-46d3-ba45-4eb65fe02790'; $order = $this->orderService->getOrderByIdentifier($orderIdentifier); // Get the cart to merge $existingCart = $this->cartResolver->resolveCart(); $reorderCart = $this ->reorderService ->addToCartFromOrder($order, $this->reorderService->createReorderCart($order)); // Merge the carts into the target cart and delete the merged carts $reorderCart = $this->cartService->mergeCarts($reorderCart, true, $existingCart); ``` # Quick order Editions: Commerce The quick order form allows users to efficiently process orders with multiple items in bulk through the storefront. Customers don't need to browse the countless store pages, they can fill in a provided form with product code (SKU) and quantity, or upload their own list into the system directly. Quick order forms can be used by registered and guest users. ## Quick order flows Customers can use one or both of the following methods to specify products and place a quick order. ### Customer enters individual products 1. Customer clicks the **Quick order** link. 1. Provides product code and quantity. At this point, no validation is provided. *[Image: Customer enters individual products]* 3. Customer clicks **Add to cart** to add items to the cart and finish an ordering process. 1. In the cart section, the availability of the entered product is checked, and the customer is informed if any of them is unavailable or quantity is insufficient. ### Customer uploads list of products 1. Customer clicks the **Quick order** link. 1. Downloads a sample file from **Add your order** section. 1. Uses the template to fill in their order with product code and quantity. 1. Uploads the filled in quick order file back to the system by drag and drop or file selection. 1. The file name appears in the **Add your order** section. At this point, only file validation is provided. Product codes and availability aren't validated. *[Image: Customer uploads list of products]* 6. Customer clicks **Add to cart** to add items to the cart and finish an ordering process. 1. In the cart section, the file format and provided data are validated, the availability of the entered product is checked, and the customer is informed if any of them is unavailable or quantity is insufficient. ## Validation Orders from quick order are validated in the cart. There, the system checks if: - provided product code is valid - provided products are available for purchase - requested quantities of products are available ## Layout To change the quick order form template, go to [customize storefront layout](https://doc.ibexa.co/en/latest/templating/layout/customize_storefront_layout/index.md). ## Configuration You can configure a quick order form in the following ways. ### Size limit To change the size limit for the uploaded order file, add new value under the `ibexa.system..cart` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa: system: : cart: batch_order: file_size_limit: 512k ``` ### Processed records limit To change the size limit for the processed records, add new value under the `ibexa.system..cart` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa: system: : cart: batch_order: processed_records_limit: 2000 ``` # Shopping list Editions: LTS Update, Commerce A shopping list allows users to save potential purchases, recurring product sets, and other items for future use in the cart. A user can have several shopping lists, including a default one named "My Wishlist". ## Getting Started - [Shopping list feature guide](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/commerce/shopping_list/shopping_list_guide/): A shopping list allows users to save potential purchases, recurring product sets, and other items for future use in the cart. - [Install shopping list](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/commerce/shopping_list/install_shopping_list/): Install the Shopping list LTS update. ## Development - [Shopping list design](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/commerce/shopping_list/shopping_list_design/): Learn how to integrate the shopping list features to your own online store design. - [Shopping list APIs](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/commerce/shopping_list/shopping_list_api/): Manage shopping lists from PHP API or REST API. - [PHP API Reference](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/api/php_api/php_api_reference/namespaces/ibexa-contracts-shoppinglist.html): Ibexa\\Contracts\\ShoppingList - [Shopping list events](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/api/event_reference/shopping_list_events/): Events that are triggered while managing shopping lists. - [Shopping list search criteria reference](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/search/shopping_list_search_reference/shopping_list_criteria/): Shopping list search criteria help define and fine-tune search queries for shopping lists. - [Shopping list search sort clauses reference](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/search/shopping_list_search_reference/shopping_list_sort_clauses/): Shopping list search sort clauses help define result order of search queries for shopping lists. - [Policies](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/permissions/policies/#shopping-lists): Policies are the main building block of the permissions system which lets you define the accesses for specific user roles. - [Limitation reference](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/permissions/limitation_reference/#shopping-list-limitation): Limitations let you fine-tune the permission system by specifying limits to roles granted to users. - [REST API Reference](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Shopping-List): commerce/shopping-listresources # Shopping list feature guide Editions: LTS Update, Commerce Shopping lists give logged-in customers a simple yet powerful way to manage future purchases. They can use it to save potential purchases, recurring product sets, and other items for future use in the cart. ## Availability The shopping list feature is available for [Commerce edition](https://doc.ibexa.co/en/latest/ibexa_products/ibexa_commerce/index.md) as an [LTS update](https://doc.ibexa.co/en/latest/ibexa_products/editions/#lts-updates) since v5.0.6. ## Use cases Shopping lists can be used in various ways, depending on the customer's needs and preferences. Here are some examples. ### Recurrent purchases Every quarter, almost the same consumables must be bought. Thanks to a dedicated shopping list, the cart can be quickly drafted and filled with all the necessary products. Only quantities need to be adjusted afterward in the cart, for example, depending on what's left from previous quarter and known consumption for the same period from previous years. ### Project wishlist A customer can store every purchase needed by an incoming project in a dedicated shopping list, even several products fulfilling the same purpose to decide later which ones to keep in the final cart. ## Shopping list management overview Use policies to control rights to create, view, edit, and delete shopping lists. Authenticated customers can be granted with these rights, and additionally restricted to interact only with their own shopping lists. For more information, see [Shopping list user role](https://doc.ibexa.co/en/latest/commerce/shopping_list/install_shopping_list/#shopping-list-user-role). A customer always have a default shopping list named “My Wishlist” which is created automatically on first use. It can't be renamed and can't be deleted. You can configure how many shopping lists a customer can have, and how much products they can contain. For more information, see [Configure shopping list](https://doc.ibexa.co/en/latest/commerce/shopping_list/install_shopping_list/#configure). A shopping list only stores product codes. A shopping list doesn't store quantities. In the out-of-the-box [storefront](https://doc.ibexa.co/en/latest/commerce/storefront/storefront/index.md), a shopping list user can: - Create a shopping list - in shopping lists management interface *[Image: Shopping lists management interface with an highlight on the "Create" button]* - from catalog when adding a product to a shopping list *[Image: Product "Add to cart" button and "Add to list" drop-down menu zone with an highlight on the "+ Create an new shopping list" option]* - from a shopping list when adding a product to another shopping list *[Image: Shopping list product list with three-dots drop-down menu having "Add to shopping list", the "Add to shopping list" pop-in modal with an highlight on the "+ Create an new shopping list" option]* - Manage to which shopping lists a product (or product variant) belongs to, from a product page or from a shopping list's product list *[Image: "Add to shopping list" belonging menu with "My Wishlist" selected]* *[Image: "Add to shopping list" belonging menu with "Bikes" selected]* - Rename a shopping list (except the default “My Wishlist”) - View the list of their shopping lists - View a shopping list and its product list - Copy product from a shopping list to cart (product is kept in shopping list while added to the cart, quantity in the cart is incremented by 1 each time) - Copy a whole shopping list to cart - products are kept in shopping list while added to the cart - products out-of-stock aren't copied and the user is warned - product quantities are incremented by 1, the user can adjust quantities in the cart *[Image: Shopping list product list with highligts on "Add to cart" and "Add all to cart" buttons]* - Move a product from cart to “My Wishlist” (product is removed from cart and added to the default shopping list) - Move the whole cart to “My Wishlist” (products are removed from cart and added to the default shopping list) *[Image: Cart's product list with highligts on "Move to My Wishlist" and "Move all to wishlist" buttons]* - Delete a shopping list ## Extensibility The shopping list's [PHP API](https://doc.ibexa.co/en/latest/commerce/shopping_list/shopping_list_api/#php-api) and [REST API](https://doc.ibexa.co/en/latest/commerce/shopping_list/shopping_list_api/#rest-api) already offer few functionalities not used in the default storefront, such as emptying shopping lists, or moving products from a cart to a specific shopping list. You can use these APIs to implement custom feature, or extend them even further to cover more use cases. # Install shopping list Editions: LTS Update, Commerce ## Install framework Run the following command to install the package: ``` composer require ibexa/shopping-list ``` The associated Symfony Flex recipe configures the bundle and its routes. Check that the following line has been added by the recipe to `config/bundles.php` file's array: ``` Ibexa\Bundle\ShoppingList\IbexaShoppingListBundle::class => ['all' => true], ``` And that you have a `config/routes/ibexa_shopping_list.yaml` file configuring the following routes: ``` ibexa.shopping_list: resource: '@IbexaShoppingListBundle/Resources/config/routing.php' ibexa.rest.shopping_list: resource: '@IbexaShoppingListBundle/Resources/config/routing_rest.php' prefix: '%ibexa.rest.path_prefix%' ``` ## Modify database schema Add the tables needed by the bundle: **MySQL** ``` CREATE TABLE ibexa_shopping_list ( id INT AUTO_INCREMENT NOT NULL, owner_id INT NOT NULL, identifier CHAR(36) NOT NULL COMMENT '(DC2Type:guid)', name VARCHAR(190) DEFAULT NULL, created_at DATETIME NOT NULL COMMENT '(DC2Type:datetime_immutable)', updated_at DATETIME NOT NULL COMMENT '(DC2Type:datetime_immutable)', is_default TINYINT(1) DEFAULT 0 NOT NULL, UNIQUE INDEX ibexa_shopping_list_identifier_idx (identifier), INDEX ibexa_shopping_list_owner_idx (owner_id), INDEX ibexa_shopping_list_default_idx (is_default), PRIMARY KEY(id) ) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_520_ci` ENGINE = InnoDB; CREATE TABLE ibexa_shopping_list_entry ( id INT AUTO_INCREMENT NOT NULL, shopping_list_id INT NOT NULL, product_code VARCHAR(64) NOT NULL, identifier CHAR(36) NOT NULL COMMENT '(DC2Type:guid)', added_at DATETIME NOT NULL COMMENT '(DC2Type:datetime_immutable)', UNIQUE INDEX ibexa_shopping_list_entry_identifier_idx (identifier), INDEX ibexa_shopping_list_entry_list_idx (shopping_list_id), INDEX ibexa_shopping_list_entry_product_idx (product_code), INDEX ibexa_shopping_list_entry_added_at_idx (added_at), UNIQUE INDEX ibexa_shopping_list_entry_unique (shopping_list_id, product_code), PRIMARY KEY(id) ) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_520_ci` ENGINE = InnoDB; ALTER TABLE ibexa_shopping_list ADD CONSTRAINT ibexa_shopping_list_owner_fk FOREIGN KEY (owner_id) REFERENCES ibexa_user (contentobject_id) ON UPDATE CASCADE ON DELETE CASCADE; ALTER TABLE ibexa_shopping_list_entry ADD CONSTRAINT ibexa_shopping_list_entry_list_fk FOREIGN KEY (shopping_list_id) REFERENCES ibexa_shopping_list (id) ON UPDATE CASCADE ON DELETE CASCADE; ALTER TABLE ibexa_shopping_list_entry ADD CONSTRAINT ibexa_shopping_list_entry_product_fk FOREIGN KEY (product_code) REFERENCES ibexa_product (code) ON UPDATE CASCADE ON DELETE CASCADE; ``` **PostgreSQL** ``` CREATE TABLE ibexa_shopping_list ( id SERIAL NOT NULL, owner_id INT NOT NULL, identifier UUID NOT NULL, name VARCHAR(190) DEFAULT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, updated_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, is_default BOOLEAN DEFAULT false NOT NULL, PRIMARY KEY (id) ); CREATE UNIQUE INDEX ibexa_shopping_list_identifier_idx ON ibexa_shopping_list (identifier); CREATE INDEX ibexa_shopping_list_owner_idx ON ibexa_shopping_list (owner_id); CREATE INDEX ibexa_shopping_list_default_idx ON ibexa_shopping_list (is_default); COMMENT ON COLUMN ibexa_shopping_list.created_at IS '(DC2Type:datetime_immutable)'; COMMENT ON COLUMN ibexa_shopping_list.updated_at IS '(DC2Type:datetime_immutable)'; CREATE TABLE ibexa_shopping_list_entry ( id SERIAL NOT NULL, shopping_list_id INT NOT NULL, product_code VARCHAR(64) NOT NULL, identifier UUID NOT NULL, added_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY (id) ); CREATE UNIQUE INDEX ibexa_shopping_list_entry_identifier_idx ON ibexa_shopping_list_entry (identifier); CREATE INDEX ibexa_shopping_list_entry_list_idx ON ibexa_shopping_list_entry (shopping_list_id); CREATE INDEX ibexa_shopping_list_entry_product_idx ON ibexa_shopping_list_entry (product_code); CREATE INDEX ibexa_shopping_list_entry_added_at_idx ON ibexa_shopping_list_entry (added_at); CREATE UNIQUE INDEX ibexa_shopping_list_entry_unique ON ibexa_shopping_list_entry (shopping_list_id, product_code); COMMENT ON COLUMN ibexa_shopping_list_entry.added_at IS '(DC2Type:datetime_immutable)'; ALTER TABLE ibexa_shopping_list 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; ALTER TABLE ibexa_shopping_list_entry 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; ALTER TABLE ibexa_shopping_list_entry 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; ``` The script creates the required data structures, but doesn't add any data to the database. The users don't have any shopping lists, not even the default “My Wishlist” list. The default shopping list is created automatically when the user triggers the "Add to wishlist" action for the first time. ## Configure By default, the maximum shopping list count per user is 10 and the maximum entries per list is 100. When listing their shopping lists, the user see 25 lists per page (and as it's over the shopping list count, there is always one page of shopping lists in this default scenario). You can override the following parameters to change their values: ``` parameters: ibexa.site_access.config.default.shopping_list.limits.max_lists_per_user: 10 ibexa.site_access.config.default.shopping_list.limits.max_entries_per_list: 100 ibexa.site_access.config.default.shopping_list.pagination.list_per_page_limit: 25 ``` > **Caution: Max lists per user and default shopping list** > > The customer can always create the default shopping list if it doesn't exist yet, even if they have already reached the limit defined by `max_lists_per_user`. So, for 10 as the default limit, the user may have 11 lists if the user created 10 custom lists before creating the default one. If you want to restrict users to only the default shopping list, you can set `max_lists_per_user` to 0. ### Shopping list user role To allow customers to use the shopping list feature, create a new role and assign it to registered customer groups. To restrict authenticated users access to only their own lists, you must grant the four functions from the Shopping List module with the limitation 'Shopping List Owner: Self'. Otherwise, they will be able to interact with all shopping lists existing in the system. Anonymous users can't have shopping lists as they're internally sharing the same account. To create such role, you can use a [migration file](https://doc.ibexa.co/en/latest/content_management/data_migration/importing_data/#roles), for example, with the following content: ``` - type: role mode: create metadata: identifier: Shopping List User policies: - module: shopping_list function: create limitations: - identifier: ShoppingListOwner values: [self] - module: shopping_list function: view limitations: - identifier: ShoppingListOwner values: [self] - module: shopping_list function: edit limitations: - identifier: ShoppingListOwner values: [self] - module: shopping_list function: delete limitations: - identifier: ShoppingListOwner values: [self] ``` On a clean install, you can, for example, assign this "Shopping List User" role to the "Customers" user group. After placing the migration content in `src/Migrations/Ibexa/migrations/shopping_list_user.yaml`, you can import and execute it with: ``` php bin/console ibexa:migrations:migrate --file=shopping_list_user.yaml --siteaccess=admin ``` # Shopping list design Editions: LTS Update, Commerce To integrate the shopping list features to your own online store design, you can - look at the default shopping list templates for the `standard` theme in `vendor/ibexa/shopping-list/src/bundle/Resources/views/themes/standard/shopping_list/` directory - look at their overrides and complements in the [`storefront` theme](https://doc.ibexa.co/en/latest/commerce/storefront/storefront/index.md) at `vendor/ibexa/storefront/src/bundle/Resources/views/themes/storefront/shopping_list/` ## "Add to shopping list" widget This widget contains a list of shopping lists indicating whether a product belongs to given list and allows to create a new shopping list on the fly. It's used in the `storefront` theme in several places, embedded within a drop-down menu or a modal. *[Image: "Add to shopping list" belonging menu with "Bikes" selected]* You can use the following Twig and TypeScript components to insert an "Add to shopping list" widget for a product into your storefront: - `vendor/ibexa/shopping-list/src/bundle/Resources/views/themes/standard/shopping_list/component/add_to_shopping_list/add_to_shopping_list.html.twig` displays a list of shopping lists preceded with checkboxes showing if the product is in it. - `vendor/ibexa/shopping-list/src/bundle/Resources/public/js/component/add.to.shopping.list.ts` handles the interaction with the list of shopping lists' checkboxes and the new shopping list creation on the fly. - `vendor/ibexa/shopping-list/src/bundle/Resources/public/js/component/shopping.list.ts` handles the REST API calls. - `vendor/ibexa/shopping-list/src/bundle/Resources/public/js/component/shopping.lists.list.ts` handles the list of shopping lists. The following example shows the setup of an "Add to shopping list" widget on a product full view page in the `standard` theme without implying the `storefront` theme. For a base product, the variants are listed with an instance of the widget to demonstrate that it can be used several time on the same page. Create an `assets/js/add-to-shopping-list.ts` that initializes the `ShoppingList` object and imports the script handling the widget interactions: ``` // Shopping list service import ShoppingList from '@ibexa-shopping-list/src/bundle/Resources/public/js/component/shopping.list'; // The Add to shopping list interaction import { AddToShoppingList } from '@ibexa-shopping-list/src/bundle/Resources/public/js/component/add.to.shopping.list'; // List of all user's shopping lists import { ShoppingListsList } from '@ibexa-shopping-list/src/bundle/Resources/public/js/component/shopping.lists.list'; (function (global: Window, doc: Document) { const shoppingList = new ShoppingList(); shoppingList.init(); // Fetch user's shopping lists const addToShoppingListsNodes = doc.querySelectorAll('.ibexa-sl-add-to-shopping-list'); addToShoppingListsNodes.forEach((addToShoppingListNode) => { const addToShoppingList = new AddToShoppingList({ node: addToShoppingListNode, ListClass: ShoppingListsList }); addToShoppingList.init(); }); })(window, window.document); ``` Edit the `webpack.config.js` to enable TypeScript, set the aliases used in `add-to-shopping-list.ts`, and create an entry for it: ``` // […] //Encore.addEntry('app', './assets/app.js'); Encore .enableTypeScriptLoader() .addAliases({ '@ibexa-shopping-list': path.resolve('./vendor/ibexa/shopping-list'), '@ibexa-admin-ui': path.resolve('./vendor/ibexa/admin-ui'), // @ibexa-admin-ui/…/text.helper dependency }) .addEntry('add-to-shopping-list-js', [ path.resolve(__dirname, './assets/js/add-to-shopping-list.ts'), ]) ; const projectConfig = Encore.getWebpackConfig(); projectConfig.name = 'app'; module.exports = [...customConfigs, projectConfig]; ``` Then, you can use the component in your template as in the following example: ``` {% block meta %} {{ parent() }} {# The CSRF token and SiteAccess are needed for the REST API calls #} {% endblock %} {% block content %} {{ product.name }} {% include '@ibexadesign/shopping_list/component/add_to_shopping_list/add_to_shopping_list.html.twig' with { product_code: product.code, } %} {% endblock %} {% block javascripts %} {{ encore_entry_script_tags('add-to-shopping-list-js') }} {% endblock %} ``` To have a more complete example, let's continue with a product full view template which could work on a fresh installation. In `src/Controller/ProductViewController.php`, create a new controller to add the variants to the product view: ``` productService->getProductFromContent($view->getContent()); if ($product->isBaseProduct()) { $view->addParameters([ 'variants' => new BatchIterator(new ProductVariantFetchAdapter( $this->productService, $product, new ProductVariantQuery(), )), ]); } return $view; } } ``` In `templates/themes/standard/full/product.html.twig`, create a template to render the product in full view: ``` {% extends '@ibexadesign/pagelayout.html.twig' %} {% set product = content|ibexa_get_product %} {% block meta %} {% set token = csrf_token ?? csrf_token(ibexa_get_rest_csrf_token_intention()) %} {% endblock %} {% block content %} {{ ibexa_content_name(content) }} {{ product.code }} {% if not product.isBaseProduct() and can_view_shopping_list and can_edit_shopping_list %} {% set component %} {% include '@ibexadesign/shopping_list/component/add_to_shopping_list/add_to_shopping_list.html.twig' with { product_code: product.code, } %} {% endset %} {{ _self.add_to_shopping_list(product, component) }} {% endif %} {% if product.isBaseProduct() %}
      {% for variant in variants %}
    • {{ variant.name }} {{ variant.code }} {% if can_view_shopping_list and can_edit_shopping_list %} {% set component %} {% include '@ibexadesign/shopping_list/component/add_to_shopping_list/add_to_shopping_list.html.twig' with { product_code: variant.code, } %} {% endset %} {{ _self.add_to_shopping_list(variant, component) }} {% endif %}
    • {% endfor %}
    {% endif %} {% endblock %} {% block javascripts %} {{ encore_entry_script_tags('add-to-shopping-list-js') }} {% endblock %} {% macro add_to_shopping_list(product, component) %} {% set widget_id = 'add-to-shopping-list-' ~ product.code|slug %}
    {% endmacro %} ``` Because the component uses global variables, it can't be used directly in a macro. In `config/packages/views.yaml`, configure the controller and template used to render the product full view: ``` ibexa: system: default: content_view: full: product: controller: 'App\Controller\ProductViewController::viewAction' template: '@ibexadesign/full/product.html.twig' match: '@Ibexa\Contracts\ProductCatalog\ViewMatcher\ProductBased\IsProduct': true ``` ## `ShoppingList` JS class and `ibexaShoppingList` global The `ShoppingList` class is responsible for handling the shopping lists data and interactions with the REST API. An object of this class contains the shopping lists and their entries, and has methods to manipulate the shopping lists. An object of this class can be initialized with the `shoppingList.init()` function only once. This initialization creates the `window.ibexaShoppingList` global variable pointing to the object. If you have several scripts needing an instance of `ShoppingList` class, `window.ibexaShoppingList` is the indicator if it has been initialized already and it points to the object you should use. Preferably initialize an object of class `ShoppingList` on the top of the script, then use `window.ibexaShoppingList` in the next lines. It has the following methods: - `createShoppingList(name)` creates a new shopping list, updates the local `window.ibexaShoppingList.shoppingLists` property, and returns a [`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise) resolving to an array with - at index 0, the created shopping list - at index 1, the whole `ShoppingList` object with all the user's shopping lists - `getShoppingLists()` returns the local `window.ibexaShoppingList.shoppingLists` property - `loadShoppingLists()` loads the shopping lists from the server, then updates the local `window.ibexaShoppingList.shoppingLists` property, and returns it - `loadShoppingList(list_identifier: string)` returns a `Promise` for the shopping list with the given identifier - `addShoppingListEntries(list_identifier: string, product_codes: string[])` adds entries to the given shopping list for the given product codes, and returns a `Promise` for the [`Response`](https://developer.mozilla.org/docs/Web/API/Response) - `removeShoppingListEntries(list_identifier: string, entry_identifiers: string[])` remove from the given shopping list the given entries, and returns a `Promise` resolving to a `Response` `window.ibexaShoppingList.shoppingLists` has the following data structure: ``` shoppingLists_Mockup = { totalCount: 2, count: 2, ShoppingList: [ { identifier: "12345678-1234-1234-1234-123456789abc", name: "My Wishlist", isDefault: true, owner: {_href: "/api/ibexa/v2/user/users/…", '_media-type': "application/vnd.ibexa.api.User+json"}, entries: [ { identifier: "…", product: { _href: "/api/ibexa/v2/product/catalog/products/PRODUCT_CODE", '_media-type': "application/vnd.ibexa.api.Product+json", code: "PRODUCT_CODE", name: "Product name" }, addedAt: "YYYY-MM-DD hh:mm:ss" } ], createdAt: "YYYY-MM-DD hh:mm:ss", updatedAt: "YYYY-MM-DD hh:mm:ss" }, { identifier: "325d1f8d-877d-40bf-9389-e8eb3e0de58a", name: "My own custom list", isDefault: false, owner: {_href: "/api/ibexa/v2/user/users/…", '_media-type': "application/vnd.ibexa.api.User+json"}, entries: [ { identifier: "…", product: { _href: "/api/ibexa/v2/product/catalog/products/ANOTHER_PRODUCT_CODE", '_media-type': "application/vnd.ibexa.api.Product+json", code: "ANOTHER_PRODUCT_CODE", name: "Another product name" }, addedAt: "YYYY-MM-DD hh:mm:ss" } ], createdAt: "YYYY-MM-DD hh:mm:ss", updatedAt: "YYYY-MM-DD hh:mm:ss" } ] }; ``` Remember that a `ShoppingList` object like the `window.ibexaShoppingList` has its data updated by the `ShoppingList.createShoppingList` and `ShoppingList.loadShoppingLists` methods. The following script creates a shopping list, adds a product to it, then refreshes the local `window.ibexaShoppingList.shoppingLists` (as `addShoppingListEntries` method doesn't do it): ``` if (!window.ibexaShoppingList) { throw new Error('ShoppingList object not initialized, window.ibexaShoppingList not defined'); } let product_code = ''; let shopping_list_name = ''; window.ibexaShoppingList.createShoppingList(shopping_list_name).then((data) => { window.ibexaShoppingList.addShoppingListEntries(data[0].identifier, [product_code]).then(() => { window.ibexaShoppingList.loadShoppingLists(); // Refresh local object }); }); ``` If the "Add to shopping list" widget is used, it could be updated with the following addition to the previous script: ``` window.ibexaShoppingList.createShoppingList(shopping_list_name).then((data) => { let shopping_list_identifier = data[0].identifier; window.ibexaShoppingList.addShoppingListEntries(shopping_list_identifier, [product_code]).then(() => { window.ibexaShoppingList.loadShoppingLists().then(() => { let selector = '.ibexa-sl-add-to-shopping-list[data-product-code="' + product_code + '"] input[type="checkbox"][value="' + shopping_list_identifier + '"]'; document.querySelector(selector).checked = true; // Check the new list in product's "Add to shopping list" widget }); }); }); ``` ## JavaScript events ### Shopping lists data changed event The `ibexa-shopping-list:shopping-lists-data-changed` event is dispatched by the `document.body` - on `ShoppingList.init()` (and the `window.ibexaShoppingList` global variable is set) - on `ShoppingList.createShoppingList()` (and the `window.ibexaShoppingList` global variable is updated) - on `ShoppingList.addShoppingListEntries()` - on `ShoppingList.removeShoppingListEntries()` ``` document.body.addEventListener('ibexa-shopping-list:shopping-lists-data-changed', (event) => { console.log(event, window.ibexaShoppingList); }) ``` ### Prepare request event The `ibexa-shopping-list:prepare-request` event is dispatched by the `document` before each REST API call, with the request details in the event's `detail` property. ``` document.addEventListener('ibexa-shopping-list:prepare-request', (event) => { console.log(event, event.detail.request); }) ``` ## Built-in views Some routes lead to views (when used with `GET` method) through controllers from the `\Ibexa\Bundle\ShoppingList\Controller` namespace. Each uses a template which receives one or several variables, including forms to handle user interactions. | Route path, name, and controller | Template | Available variables | Description | | ---------------------------------------------------------------------------------------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | | `GET /shopping-list` `ibexa.shopping_list.list` `ShoppingListListController` | `@ibexadesign/shopping_list/list.html.twig` | `shopping_lists` (`Pagerfanta`), `bulk_delete_form`, `filter_form` | List of shopping lists | | `GET /shopping-list/create` `ibexa.shopping_list.create` `ShoppingListCreateController` | `@ibexadesign/shopping_list/create.html.twig` | `form` | Form to create a new shopping list | | `GET /shopping-list/{identifier}` `ibexa.shopping_list.view` `ShoppingListViewController` | `@ibexadesign/shopping_list/view.html.twig` | `move_entries_form`, `remove_entries_form`, `clear_form`, `delete_form` | Shopping list display | | `GET /shopping-list/{identifier}/update` `ibexa.shopping_list.update` `ShoppingListUpdateController` | `@ibexadesign/shopping_list/update.html.twig` | `shopping_list`, `form` | Form to rename a shopping list | | `GET /shopping-list/add` `ibexa.shopping_list.add` `AddProductToShoppingListController` | `@ibexadesign/shopping_list/add.html.twig` | `products` ([`ProductListInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-ProductListInterface.html)), `forms` (associative array of forms indexed on product code) | List of products with for each the form to add it to a shopping list | For all those templates (except `add.html.twig`), you'll find two implementations: - a generic one for the `standard` theme in `vendor/ibexa/shopping-list/src/bundle/Resources/views/themes/standard/` - a more advanced demo one for the `storefront` theme in `vendor/ibexa/storefront/src/bundle/Resources/views/themes/storefront/` Instead of using the `add` route, you should consider using the ["Add to shopping list" widget](#add-to-shopping-list-widget) first. The following example shows how to link to the shopping list listing page, using a heart icon: ``` ``` The `\Ibexa\Bundle\Storefront\EventSubscriber\ShoppingList\DetailsViewSubscriber` passes an additional `selected_entries_form` variable to the template. This form allows to have "Add to cart" button for selected entries on top of the shopping list view in `storefront` theme through `vendor/ibexa/storefront/src/bundle/Resources/views/themes/storefront/shopping_list/view.html.twig`. ## User menu The `\Ibexa\Bundle\Storefront\EventSubscriber\ShoppingList\UserMenuSubscriber` is responsible for adding the "Shopping lists" item between "Orders" and "Change password" to the user menu previously initiated by the `\Ibexa\Bundle\Storefront\Menu\Builder\UserMenuBuilder`. You can look at how this subscriber tests that the user isn't anonymous and then has the [`shopping_list/view` policy](https://doc.ibexa.co/en/latest/permissions/policies/#shopping-lists) ([`\Ibexa\Contracts\ShoppingList\Permission\Policy\ShoppingList\View`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-Permission-Policy-ShoppingList-View.html)) before adding the "Shopping lists" item. # Shopping list APIs Editions: LTS Update, Commerce The shopping list APIs allow managing shopping lists. The cart APIs includes methods to move products from cart to shopping list and vice versa. ## About the default shopping list There is one default shopping list per user. This default shopping list is created only when a user uses it for the first time. The default shopping list is created by [`\Ibexa\Contracts\ShoppingList\ShoppingListServiceInterface::getOrCreateDefaultShoppingList()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-ShoppingListServiceInterface.html#method_getOrCreateDefaultShoppingList). For example, starting to use the default list from REST API will create it if it doesn't exist, as during a call to [`POST /shopping-list/default/entries`](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Shopping-List/operation/api_shopping-listdefaultentries_post) or [`POST /cart/{identifier}/move-to-shopping-list`](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Cart/operation/api_cart_identifiermove-to-shopping-list_post). Note that `default` isn't the default shopping list identifier. Each user's default shopping list has a unique identifier, a hash string like `01234567-89ab-cdef-0123-456789abcdef`. When a user has permissions to create shopping lists [`shopping_list/create`](https://doc.ibexa.co/en/latest/permissions/policies/#shopping-lists), they can always create a default shopping list, regardless of the maximum shopping list count per user configuration [`max_lists_per_user`](https://doc.ibexa.co/en/latest/commerce/shopping_list/install_shopping_list/#configure). ## PHP API In the [`Ibexa\Contracts\ShoppingList`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/namespaces/ibexa-contracts-shoppinglist.html) namespace are the interfaces to manipulate shopping lists. The [`Ibexa\Contracts\ShoppingList\ShoppingListServiceInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-ShoppingListServiceInterface.html) defines methods to create, get, find, update, clear, and delete shopping lists, and to add, get, move, and remove entries. ### List and search shopping lists Shopping list search can be done with [`ShoppingListServiceInterface::findShoppingLists()` method](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-ShoppingListServiceInterface.html#method_findShoppingLists) with a [`ShoppingListQuery`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-Value-ShoppingListQuery.html) built with criteria from the [`Criterion` namespace](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/namespaces/ibexa-contracts-shoppinglist-value-query-criterion.html) implementing the [`CriterionInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-Value-Query-CriterionInterface.html), and with sort clauses from the [`SortClause` namespace](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/namespaces/ibexa-contracts-shoppinglist-value-query-sortclause.html). To get all shopping lists (of the current user or of the whole repository depending on the current user limitation), use the search method without criterion: ``` $lists = $this->shoppingListService->findShoppingLists(new ShoppingListQuery()); ``` For more information about the shopping list search, see [Shopping list criteria](https://doc.ibexa.co/en/latest/search/shopping_list_search_reference/shopping_list_criteria/index.md), and [Shopping list sort clauses](https://doc.ibexa.co/en/latest/search/shopping_list_search_reference/shopping_list_sort_clauses/index.md) references. ### Manage shopping lists entries Methods editing the shopping list first store the change in the persistence layer then return the updated shopping list object. If you forgot to retrieve this result in your variable, the local object isn't synchronized with the database. In the following example, if some assignments (`$list =`) are removed, the dumped `$list` object doesn't contain the stored shopping list at that time. If only the middle assignment is removed, the last dumped variable contains the up-to-date shopping list. ``` $list = $this->shoppingListService->getOrCreateDefaultShoppingList(); dump($list); $list = $this->shoppingListService->clearShoppingList($list); dump($list); $list = $this->shoppingListService->addEntries($list, [new EntryAddStruct($productCode)]); dump($list); ``` When adding array of entries with `ShoppingListService::addEntries()`, an exception is thrown if at least product is already in the shopping list and no entries are added to the list. The following example adds products to a shopping list while avoiding error on duplicated entries. In this example the duplicates are ignored, but you could extend it to, for example, notify the user about each found duplicate. ``` $filteredProductCodes = array_filter( $desiredProductCodes, static fn ($productCode): bool => !$list->getEntries()->hasEntryWithProductCode($productCode) ); $list = $this->shoppingListService->addEntries( $list, array_map( static fn ($productCode): EntryAddStruct => new EntryAddStruct($productCode), $filteredProductCodes ) ); ``` The following example moves products from a source shopping list to a target shopping list after filtering out products already in the target list: ``` $entriesToRemove = []; $entriesToAdd = []; foreach ($movedProductCodes as $productCode) { if ($sourceList->getEntries()->hasEntryWithProductCode($productCode)) { $entriesToRemove[] = $sourceList->getEntries()->getEntryWithProductCode($productCode); if (!$targetList->getEntries()->hasEntryWithProductCode($productCode)) { $entriesToAdd[] = new EntryAddStruct($productCode); } } } $sourceList = $this->shoppingListService->removeEntries($sourceList, $entriesToRemove); $targetList = $this->shoppingListService->addEntries($targetList, $entriesToAdd); ``` ### Transfer between shopping list and cart Interactions between shopping list and cart are managed by [`Ibexa\Contracts\Cart\CartShoppingListTransferServiceInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Cart-CartShoppingListTransferServiceInterface.html) The following example starts with an empty cart and an empty shopping list, then adds a product to the shopping list and copies it twice to the cart. It continues with moving the whole cart to an empty list. ``` $this->cartService->emptyCart($cart); $list = $this->shoppingListService->clearShoppingList($list); $list = $this->shoppingListService->addEntries($list, [new ShoppingListEntryAddStruct($productCode)]); $entry = $list->getEntries()->getEntryWithProductCode($productCode)->getIdentifier(); // Get entry's automatically generated identifier $cart = $this->cartShoppingListTransferService->addSelectedEntriesToCart($list, [$entry], $cart); $cart = $this->cartShoppingListTransferService->addSelectedEntriesToCart($list, [$entry], $cart); dump( $list->getEntries()->hasEntryWithProductCode($productCode), // true as the entry is copied and not moved $cart->getEntries()->getEntryForProduct($this->productService->getProduct($productCode))->getQuantity() // 2 as the entry was added twice ); $list = $this->shoppingListService->clearShoppingList($list); // Empty the list to avoid duplicate and test the move from cart $list = $this->cartShoppingListTransferService->moveCartToShoppingList($cart, $list); $cart = $this->cartService->getCart($cart->getIdentifier()); // Refresh local object from persistence dump( $list->getEntries()->hasEntryWithProductCode($productCode), // true as, after the clear, the entry is moved from cart $cart->getEntries()->hasEntryForProduct($this->productService->getProduct($productCode)) // false as the entry was moved ); ``` ### Events When the shopping list service methods are called, event are dispatched before and after the action so its parameters or results can be customized. For more information, see [Shopping list event reference](https://doc.ibexa.co/en/latest/api/event_reference/shopping_list_events/index.md). There is no specific event for the transfer operations. - When adding from shopping list to cart, the [`Ibexa\Contracts\Cart\Event\BeforeAddEntryEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Cart-Event-BeforeAddEntryEvent.html) and [`Ibexa\Contracts\Cart\Event\AddEntryEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Cart-Event-AddEntryEvent.html) are dispatched for each entry that wasn't previously in the cart. - When moving from cart to shopping list, single [`Ibexa\Contracts\ShoppingList\Event\BeforeAddEntriesEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-Event-BeforeAddEntriesEvent.html) and [`Ibexa\Contracts\ShoppingList\Event\AddEntriesEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-Event-AddEntriesEvent.html) events are dispatched for the batch of entries, then [`Ibexa\Contracts\Cart\Event\BeforeRemoveEntryEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Cart-Event-BeforeRemoveEntryEvent.html) and [`Ibexa\Contracts\Cart\Event\BeforeRemoveEntryEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Cart-Event-RemoveEntryEvent.html) are dispatched for each entry removed from the cart. ## REST API The REST API provides resources for managing shopping lists and their entries, as well as for moving products between the cart and the shopping list. These resources start with [`/shopping-list/*`](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Shopping-List). In Symfony's `dev` environment, you can consult and test the REST API at `/api/ibexa/v2/doc#/Shopping%20List`. The following REST example uses `curl` and [`jq`](https://jqlang.org/) to: - log in a user - search for the default shopping list to get its identifier - clear the default shopping list if it exists using its identifier - add a product to the default shopping list ``` # Log in and store CSRF Token csrf_token=`curl -s -c cookie.txt -X 'POST' \ "$BASE_URL/api/ibexa/v2/user/sessions" \ -H 'accept: application/vnd.ibexa.api.Session+json' \ -H 'Content-Type: application/vnd.ibexa.api.SessionInput+json' \ -d "{ \"SessionInput\": { \"login\": \"$CUSTOMER_USERNAME\", \"password\": \"$CUSTOMER_PASSWORD\" } }" | jq -r '.Session.csrfToken'` # Get default shopping list identifier if it exists default_list_identifier=`curl -s -b cookie.txt -X 'GET' \ "$BASE_URL/api/ibexa/v2/shopping-list?isDefault=true" \ -H 'accept: application/vnd.ibexa.api.ShoppingListCollection+json' \ | jq -r '.ShoppingListCollection.ShoppingList[0].identifier'` # Clear default shopping list if [ "" != "$default_list_identifier" ]; then curl -s -b cookie.txt -X 'POST' \ "$BASE_URL/api/ibexa/v2/shopping-list/$default_list_identifier/clear" \ -H 'accept: application/vnd.ibexa.api.ShoppingList+json' \ -H "X-CSRF-Token: $csrf_token" | jq fi # Add entries to the default shopping list, # create it if it doesn't exist yet, # and get the updated data curl -s -b cookie.txt -X 'POST' \ "$BASE_URL/api/ibexa/v2/shopping-list/default/entries" \ -H 'accept: application/vnd.ibexa.api.ShoppingList+json' \ -H "X-CSRF-Token: $csrf_token" \ -H 'Content-Type: application/vnd.ibexa.api.ShoppingListEntriesAdd+json' \ -d "{ \"ShoppingListEntriesAdd\": { \"entries\": [ { \"productCode\": \"$PRODUCT_CODE\" } ] } }" | jq ``` ### Transfer between shopping list and cart You can use: - [`POST /shopping-list/{identifier}/add-entries-to-cart`](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Shopping-List/operation/api_shopping-list_identifieradd-entries-to-cart_post) to add some shopping list entries to the default cart - [`POST /shopping-list/{identifier}/add-entries-to-cart/{cartIdentifier}`](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Shopping-List/operation/api_shopping-list_identifieradd-entries-to-cart_cartIdentifier_post) to add some shopping list entries to a specific cart - [`POST /shopping-list/{identifier}/add-to-cart`](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Shopping-List/operation/api_shopping-list_identifieradd-to-cart_post) to add all entries from a shopping list to the default cart - [`POST /shopping-list/{identifier}/add-to-cart/{cartIdentifier}`](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Shopping-List/operation/api_shopping-list_identifieradd-to-cart_cartIdentifier_post) to add all entries from a shopping list to a specific cart - [`POST /cart/{identifier}/move-entries-to-shopping-list`](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Cart/operation/api_cart_identifiermove-entries-to-shopping-list_post) to move some cart entries to the default shopping list - [`POST /cart/{identifier}/move-entries-to-shopping-list/{shoppingListIdentifier}`](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Cart/operation/api_cart_identifiermove-entries-to-shopping-list_shoppingListIdentifier_post) to move some cart entries to a specific shopping list - [`POST /cart/{identifier}/move-to-shopping-list`](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Cart/operation/api_cart_identifiermove-to-shopping-list_post) to move all entries from a cart to the default shopping list - [`POST /cart/{identifier}/move-to-shopping-list/{shoppingListIdentifier}`](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Cart/operation/api_cart_identifiermove-to-shopping-list_shoppingListIdentifier_post) to move all entries from a cart to a specific shopping list # Checkout Editions: Commerce Checkout is a crucial component of the Commerce offering delivered as part of Ibexa Commerce. In a course of a multi-step process, it collects necessary transaction data, such as billing and shipping addresses, payment and shipping information. From the front-end perspective, it's a reusable component that provides access to the workflow and allows buyers to place an order for cart items. *[Image: Address selection stage]* Depending on the model of shopping process that you need to use, the checkout process can range between a straightforward and extremely complicated one. To allow for this variation, the component is highly configurable and extensible: - Like the editorial workflow, it relies on [Symfony Workflow](https://symfony.com/doc/7.4/components/workflow.html) - It exposes [PHP API](https://doc.ibexa.co/en/latest/commerce/checkout/checkout_api/index.md) that allows for workflow manipulation - It exposes Twig functions used for checkout rendering In a default implementation, users go through a series of steps. They first select a billing and shipping address, then select shipping and payment methods, later they review summary, and confirm their choices, to finally receive a simulated order confirmation. Until the checkout process is complete, at any point of the process, users can go back to the cart and modify cart information, for example, cart item quantities. They can also navigate back and forth between checkout steps, with an exception of the "Checkout complete" step, which always ends the process. You can modify these steps according to your needs. For more information, see [Configure checkout](https://doc.ibexa.co/en/latest/commerce/checkout/configure_checkout/index.md). ## Shipping and billing address assignment logic As far as shipping details are concerned, checkout can behave differently, depending on whether the buyer is a corporate account member, a registered customer, or an individual. - Corporate account members can see a company's billing address, and several shipping addresses to pick from, as predefined in the company profile. - Registered customers are able see and modify the addresses that they defined at registration - Individuals are able to enter both addresses at checkout For more information about shipping and billing addresses, see [Configure checkout](https://doc.ibexa.co/en/latest/commerce/checkout/configure_checkout/#configure-shipping-and-billing-address-field-format). ## Virtual Products checkout Virtual product is a special type of a [product](https://doc.ibexa.co/en/latest/product_catalog/products/index.md). Virtual products are non-tangible items such as memberships, services, warranties. They can be sold individually, or as part of a product bundle. Virtual products don’t require shipment when they're purchased individually. While purchasing virtual product, you only have to fill in the billing address and select relevant payment method. *[Image: Virtual product purchasing]* ## Reorder Reorder functions as the variant for the checkout workflow and is accessible solely to logged-in users. It initiates from the user's order history, where they can click **Reorder** and trigger the flow. Next, the user is moved to cart where the system validates the order against existing stock. If everything is available, customer can move to payment and summary. The system uses information from the past order to pre-fill address, shipping method, and payment details. For more information, see [reorder documentation](https://doc.ibexa.co/en/latest/commerce/checkout/reorder/index.md). # Configure checkout Editions: Commerce When you work with your Commerce implementation, you can review and modify the checkout configuration. > **Note: Permissions** > > When you modify the workflow configuration, make sure you properly set user [permissions](https://doc.ibexa.co/en/latest/permissions/permission_use_cases/#commerce) to the checkout component. ## Configure checkout workflow Checkout workflow relies on [Symfony Workflow](https://symfony.com/doc/7.4/components/workflow.html). Each transition represents a separate checkout step. By default, the checkout process is configured to render each step based on a separate set of libraries and templates. Each checkout step is handled by a controller that you configure in workflow metadata. Custom workflow implementations are defined under the `framework.workflows` key, and they must support the `Ibexa\Contracts\Checkout\Value\CheckoutInterface`. The default fallback workflow is `ibexa_checkout`, which is prepended at bundle level. The `checkout.workflow` parameter is repository-aware. To customize your configuration, place it in a YAML file, under the `framework.workflows.` key, and reference it with `ibexa.repositories..checkout.workflow: your_workflow_name`. The system can then identify which of your configured workflows handles the checkout process. > **Note: Note** > > When you modify or create a controller, to ensure that no user data is lost, extend the `Ibexa\Bundle\Checkout\Controller\AbstractStep` controller and call the `advance()` method. Each step configuration includes the following settings: - `controller` - A mandatory setting pointing to a library that governs the behavior of the process. The controller contains all the required business logic and submits the whole step, so that a transition can happen. - `next_step` - An optional name of the next workflow transition. If not provided, the next workflow-enabled transition is processed. - `label` - An optional name of the step that can be displayed in the Twig helper. - `translation_domain` - A optional setting that defines the domain for a site with translated content. By default it's set to `checkout`. ### Checkout customization example For more information about the results you can achieve by customizing checkout, see [Customize checkout](https://doc.ibexa.co/en/latest/commerce/checkout/customize_checkout/index.md). ## Configure shipping and payment methods You can define the shipping and payment methods in the user interface. For more information, see [Work with shipping methods](https://doc.ibexa.co/projects/userguide/en/5.0/commerce/shipping_management/work_with_shipping_methods/) and [Work with payment methods](https://doc.ibexa.co/projects/userguide/en/5.0/commerce/payment/work_with_payment_methods/). ## Configure shipping and billing address field format In your implementation, you may need to create custom format configurations for the shipping or billing address fields, for example, to use different address formats based on the buyer's geographical location. Field formats for the billing and shipping addresses comply with the [FieldType Address](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/addressfield/#formats) specification and can be controlled with the `billing_address_format` and `shipping_address_format` flags. They fall back to `billing` and `shipping` predefined formats by default: - `billing` is part of the `ibexa/corporate-accounts` repository - `shipping` is part of the `ibexa/checkout` bundle's default configuration To modify address formats you create custom ones. # Customize checkout Editions: Commerce When you work with your Commerce implementation, you can review and modify the checkout configuration. Checkout is an essential component of the Commerce offering. It collects data that is necessary to create an order, including: - payment method - shipping method - billing / delivery address It could also collect any other information that you find necessary. Depending on your needs, the checkout process can be either complex or straightforward. For example, if the website is selling airline tickets, you may need several [additional steps](#add-checkout-step) with passengers defining their special needs. On the other side of the spectrum would be a store that sells books with personal pickup, where [one page checkout](#create-a-one-page-checkout) would be enough. Several factors make checkout particularly flexible and customizable: - it's based on Symfony workflow - it exposes a variety of APIs - it exposes Twig functions that help you render the steps The most important contract exposed by the package is the `CheckoutServiceInterface` interface. It exposes a number of methods that you can call, for example, to load checkouts based on checkout identifier or for a specific cart. Other methods help you create, update, or delete checkouts. For more information, see [Checkout API](https://doc.ibexa.co/en/latest/commerce/checkout/checkout_api/index.md). ## Add checkout step By default, Ibexa DXP comes with a multi-step checkout process, which you can expand by adding steps. For example, if you were creating a project for selling theater tickets, you could add a step that allows users to select their seats. ### Define workflow You can create workflow definitions under the `framework.workflows` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files). Each workflow definition consists of a series of steps and a series of transitions between the steps. To create a new workflow, for example, `seat_selection_checkout`, use the default workflow that comes with the storefront module as a basis, and add a `seat_selected` step. ``` framework: workflows: seat_selection_checkout: type: state_machine audit_trail: enabled: false marking_store: type: method property: status supports: - Ibexa\Contracts\Checkout\Value\CheckoutInterface initial_marking: initialized places: - initialized - seat_selected - address_selected - shipping_selected - summarized ``` Then, add a list of transitions. When defining a new transition, within its metadata, map the transition to its controller, and set other necessary details, such as the next step and label. ``` transitions: select_seat: from: - initialized - seat_selected - address_selected - shipping_selected - summarized to: seat_selected metadata: next_step: select_address controller: App\Controller\Checkout\Step\SelectSeatStepController label: 'Select your seats' ``` ### Create controller At this point you must add a controller that supports the newly added step. In this case, you want users to select seats in the audience. In the `src/Controller/Checkout/Step` folder, create a file that resembles the following example. The controller contains a Symfony form that collects user selections. It can reuse fields and functions that come from the checkout component, for example, after you check whether the form is valid, use the `AbstractStepController::advance` method to go to the next step of the process. ``` createStepForm($step, SelectSeatType::class); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { return $this->advance($checkout, $step, $form->getData()); } return $this->render( '@ibexadesign/checkout/step/select_seat.html.twig', [ 'layout' => $this->getSeatsLayout(), 'current_step' => $step, 'checkout' => $checkout, 'form' => $form, ] ); } private function getSeatsLayout(): array { return [ 'A' => 'X,X,0,0,1,1,1,1,X,X,X,X', 'B' => '0,0,X,0,0,1,1,1,1,X,X,X', 'C' => '1,1,1,1,0,0,0,0,0,0,0,0', 'D' => '1,1,1,1,0,1,0,0,0,0,0,0', 'E' => '1,1,1,1,0,1,0,1,0,0,0,0', 'F' => '1,1,1,1,0,1,1,0,0,1,1,0', 'G' => '1,1,1,1,1,1,1,1,1,1,1,1', 'H' => '1,1,1,1,1,1,1,1,1,1,1,1', 'I' => '1,1,1,1,1,1,1,1,1,1,1,1', ]; } } ``` #### Create a form In the `src/Form/Type` folder, create a corresponding form: ``` add( 'selection', TextType::class, [ 'constraints' => [ new NotBlank(), ], 'label' => 'Selected seats: ', ] ); } } ``` ### Create Twig template You also need a Twig template to render the Symfony form. In `templates/themes/storefront/checkout/step`, create a layout that uses JavaScript to translate clicking into a grid to a change in value: ``` {% extends '@ibexadesign/checkout/layout.html.twig' %} {% block content %} {{ form_start(form) }} {% include '@ibexadesign/checkout/component/quick_summary.html.twig' with { entries_count : 1} %}

    Please select your seats

    If you prefer not to book one, we will assign a random seat for you 48 hours before the event.

    {% set class_map = { 'X': 'null', '1': 'available', '0': 'not-available' } %} {% for row_idx, row in layout %}
    {% for col_idx, seat in row|split(',') %} {% set seat_id = '%s%d'|format(row_idx, col_idx + 1) %} {% endfor %}
    {% endfor %}
    {{ form_row(form.select_seat.selection) }}
    {% include '@ibexadesign/checkout/component/actions.html.twig' with { label: 'Go to Billing & shipping address', href: ibexa_checkout_step_path(checkout, 'select_address'), } %} {{ form_end(form) }} {% endblock %} {% block stylesheets %} {{ parent() }} {{ encore_entry_link_tags('checkout') }} {% endblock %} {% block javascripts %} {{ parent() }} {{ encore_entry_script_tags('checkout') }} {% endblock %} ``` In `assets/styles/checkout.css`, add styles required to properly display your template. ``` .seats-selection-container { padding: 20px 0; display: grid; grid-auto-columns: auto; } .seat { display: inline-block; padding: 15px; border: 1px solid #ccc; } .seat-null { background: #EEEEEE; } .seat-available { background: #98de98; cursor: pointer; } .seat-not-available { background: #d38e8e; } .seat-selected { background: #0a410a; } .seats-map { grid-column: 1 / 3; grid-row: 1; } .seats-input { grid-column: 2 / 3; grid-row: 1; } ``` > **Note: Note** > > Remember to [add the new asset file to your Webpack configuration](https://doc.ibexa.co/en/latest/templating/assets/#configure-assets). ### Select supported workflow Next, you must inform the application that the configured workflow is used in your repository. You do it in repository configuration, under the `ibexa.repositories..checkout.workflow` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa: repositories: default: checkout: workflow: seat_selection_checkout ``` ### Restart application you're now ready to see the results of your work. Shut down the application, clear browser cache, and restart the application. You should be able to see a different checkout applied after you have added products to a cart. *[Image: Additional checkout step]* ## Hide checkout step By default, Ibexa DXP comes with a multi-step checkout process, which you can scale down by hiding steps. To do it, modify workflow under the `framework.workflows` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files). This example shows how to hide a 'Billing & shipping address' step. It can be used for logged-in users with billing data stored in their accounts. ``` framework: workflows: ibexa_checkout: transitions: select_address: metadata: next_step: select_shipping controller: Ibexa\Bundle\Checkout\Controller\CheckoutStep\AddressStepController::renderStepView label: 'Billing & shipping address' translation_domain: checkout physical_products_step: true hidden: true ``` ## Create a one page checkout Another way of customizing the process would be to implement a one page checkout. Such solution could work for certain industries, where simplicity is key. It's basic advantage is simplified navigation with less clicks to complete the transaction. ### Define workflow To create a one page checkout, define a workflow that has two steps, `initialized` and `completed`, and one transition, from `initialized` or `completed` to `completed`. ``` framework: workflows: one_page_checkout: type: state_machine audit_trail: enabled: false marking_store: type: method property: status supports: - Ibexa\Contracts\Checkout\Value\CheckoutInterface initial_marking: initialized places: - initialized - completed transitions: checkout_data: from: [ initialized, completed ] to: completed metadata: controller: App\Controller\Checkout\OnePageCheckoutController ``` ### Create controller Add a regular Symfony controller in project code, which reuses classes provided by the application. Within the controller, create a form that contains all the necessary fields, such as the shipping and billing addresses, together with shipping and billing methods. In the `src/Controller/Checkout` folder, create a file that resembles the following example: ``` createForm( OnePageCheckoutType::class, $checkout->getContext()->toArray(), [ 'cart' => $this->getCart($checkout->getCartIdentifier()), ] ); if ($form->isSubmitted() && $form->isValid()) { $formData = $form->getData(); $stepData = [ 'shipping_method' => [ 'identifier' => $formData['shipping_method']->getIdentifier(), 'name' => $formData['shipping_method']->getName(), ], 'payment_method' => [ 'identifier' => $formData['payment_method']->getIdentifier(), 'name' => $formData['payment_method']->getName(), ], ]; return $this->advance($checkout, $step, $stepData); } return $this->render( '@storefront/checkout/checkout.html.twig', [ 'form' => $form, 'checkout' => $checkout, ] ); } } ``` The controller can reuse fields and functions that come from the checkout component, for example, after you check whether the form is valid, use the `AbstractStepController::advance` method to go to the next step of the process. #### Create a form In the `src/Form/Type` folder, create a corresponding form: ``` add( 'payment_method', PaymentMethodChoiceType::class, [ 'constraints' => [ new NotBlank(), ], 'label' => 'Payment Method', ], ); $builder->add( 'billing_address', AddressType::class, [ 'type' => 'billing', 'constraints' => [ new NotBlank(), ], 'label' => 'Billing Address', ] ); $builder->add( 'shipping_method', ShippingMethodChoiceType::class, [ 'cart' => $options['cart'], 'constraints' => [ new NotBlank(), ], 'label' => 'Shipping Method', ] ); $builder->add( 'shipping_address', AddressType::class, [ 'type' => 'shipping', 'constraints' => [ new NotBlank(), ], 'label' => 'Shipping Address', ] ); } public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'cart' => null, ]); $resolver->setAllowedTypes('cart', ['null', CartInterface::class]); } } ``` ### Create Twig template Create a Twig template to render the Symfony form. In `templates/themes/storefront/checkout`, create a layout that iterates through all the fields and renders them. ``` {% extends '@ibexadesign/storefront/layout.html.twig' %} {% form_theme form '@ibexadesign/storefront/form_fields.html.twig' %} {% block content %} {{ form_start(form) }} {{ form_widget(form._token) }}

    Single-page checkout

    A single-page checkout uses one page to display all the elements of a standard checkout process, including payment details, billing and shipping addresses, and shipping options.

    {% for child in form %} {% if not child.rendered %}
    {{ loop.index }}

    {{ child.vars.label }}

    {{ form_widget(child) }}
    {% endif %} {% endfor %}
    {{ form_end(form) }} {% endblock %} ``` In `assets/styles/checkout.css`, add styles required to properly display your template. > **Note: Note** > > Remember to [add the new asset file to your Webpack configuration](https://doc.ibexa.co/en/latest/templating/assets/#configure-assets). ### Select supported workflow Then you have to map the single-step workflow to the repository, by replacing the default `ibexa_checkout` reference with one of `one_page_checkout`: ``` ibexa: repositories: default: checkout: workflow: one_page_checkout ``` ### Restart application To see the results of your work, shut down the application, clear browser cache, and restart the application. You should be able to see a one page checkout applied after you add products to a cart. *[Image: One page checkout]* ## Create custom strategy Create a PHP definition of the new strategy that allows for workflow manipulation. In this example, custom checkout workflow applies when specific currency code ('EUR') is used in the cart. ``` getCurrency()->getCode() === 'EUR'; } } ``` ### Add conditional step Defining strategy allows to add conditional step for workflow if needed. If you add conditional step, the checkout process uses provided workflow and goes to defined step if the condition described in the strategy is met. By default conditional step is set as null. To use conditional step you need to pass second argument to constructor in the strategy definition: ``` getCurrency()->getCode() === 'EUR'; } } ``` ### Register strategy Now, register the strategy as a service: ``` services: App\Checkout\Workflow\Strategy\NewWorkflow: tags: - name: ibexa.checkout.workflow.strategy priority: 100 ``` ### Override default workflow Next, you must inform the application that the configured workflow is used in your repository. > **Note: Note** > > The configuration allows to override the default workflow, but it's not mandatory. Checkout supports multiple workflows. You do it in repository configuration, under the `ibexa.repositories..checkout.workflow` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa: repositories: : checkout: workflow: new_workflow ``` ## Manage multiple workflows When you have multiple checkout workflows, you can specify which one to use by passing an argument with the name of the selected checkout workflow to a button or link that triggers the checkout process. ``` {% set checkout_path = path('ibexa.checkout.init', { cartIdentifier: cart_identifier, checkoutName: 'selected_checkout_name' # Reference your workflow name here }) %} ``` With this setup, you can specify which workflow to use by clicking the button or link that starts the checkout. The argument passed determines which workflow is used, providing flexibility in workflow selection. ## Define custom Address field type formats To create custom Address field type formats to be used in checkout, make the following changes in the project configuration files. First, define custom format configuration keys for `billing_address_format` and `shipping_address_format`: ``` ibexa: repositories: : checkout: #"billing" by default billing_address_format: #"shipping" by default shipping_address_format: #used in registration, uses given shipping/billing addresses to pre-populate address forms in select_address checkout step, "customer" by default customer_content_type: ``` Then, define custom address formats, which, for example, don't include the `locality` field: ``` ibexa_field_type_address: formats: : country: default: - region - street - postal_code - email - phone_number : country: default: - region - street - postal_code - email - phone_number ``` # Reorder Editions: Commerce The reorder feature allows customers to streamline the process of repeating purchases. Based on a past order identifier, the cart is recreated and validated to be eligible for reordering. ## Reorder workflow Reorder is a variant of the checkout workflow accessible exclusively to logged-in users. It has the same [configuration](https://doc.ibexa.co/en/latest/commerce/checkout/configure_checkout/index.md) and [customization](https://doc.ibexa.co/en/latest/commerce/checkout/customize_checkout/index.md) options as checkout. Customers can use the following workflow to specify orders they want to reorder and complete the purchase. 1. Logged in customer clicks **Orders** on their personal menu. 1. Selects order they want to repurchase from the list. 1. On the order details site, customer clicks **Reorder**. *[Image: Order details site - reorder]* 4. A new cart is created based on the past order identifier, and the availability of the products in the cart is validated. 1. Customer clicks **Checkout**. 1. The system pre-fills address, shipping method, and payment details using information from the past order. 1. The customer is redirected to Payment and summary section where they can edit the specified address and the payment method by clicking steps on the workflow timeline. *[Image: Reorder workflow timeline]* 8. The customer pays for the order and completes the workflow. ## Configuration Reorder is a part of checkout and as such has the same [configuration](https://doc.ibexa.co/en/latest/commerce/checkout/configure_checkout/index.md) and [customization](https://doc.ibexa.co/en/latest/commerce/checkout/customize_checkout/index.md) options as checkout. Below, you can find a few examples that demonstrate how you can modify this feature. ### Customize reorder You can modify workflow under the `framework.workflows` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files). Each workflow definition consists of a series of steps and a series of transitions between the steps. Below example shows how to set up `can_be_reordered` flag for specific order statuses. ``` framework: workflows: ibexa_order: places: !php/const Ibexa\OrderManagement\Value\Status::COMPLETED_PLACE: metadata: ... can_be_reordered: true !php/const Ibexa\OrderManagement\Value\Status::CANCELLED_PLACE: metadata: ... can_be_reordered: true ``` ## Reorder PHP API You can manage and modify reorder with a dedicated checkout and cart PHP API. ### Checkout PHP API Reorder comes with the dedicated `Ibexa\Contracts\Checkout\Reorder\ReorderService` interface. It contains helper methods and facades added over existing API to ease the order manipulation process. The following methods can be used to modify the reorder flow to fit your business needs: #### `ReorderService:addToCartFromOrder` Allows you to add items from a previous order to a cart. It uses historical data from previously ordered items even if they're no longer available. Those items are validated against available stock. The method uses the following parameters: - `$order` (OrderInterface) - the source order from which items are added to the cart - `$reorderCart` (CartInterface) - the shopping cart to which items are added Return value: - `CartInterface` - the modified shopping cart containing the items from the order #### `ReorderService:copyContext` Copies context information from a source order to a target checkout. This can include additional information or settings associated with the source order, for example, address. The method uses the following parameters: - `$sourceOrder` (OrderInterface) - the source order from which context is copied - `$targetCheckout` (CheckoutInterface) - the target checkout to which context is copied #### `ReorderService:createReorderCart` Creates a new shopping cart for reordering items from a past order in the same currency. The method uses the following parameters: - `$order` (OrderInterface) - the order for which a reorder cart is being created - `$newCartName` (optional string) - an optional name for the new cart Return value: - `CartInterface` - the newly created shopping cart #### `ReorderService:canBeReordered` Checks if a given order can be reordered. It evaluates criteria such as the order's status to determine reorder eligibility. The method uses the following parameters: - `$order` (OrderInterface) - reorder eligibility Return value: - `bool` - true if the order can be reordered, otherwise, false For more information on how to modify checkout, see [Checkout API documentation](https://doc.ibexa.co/en/latest/commerce/checkout/checkout_api/index.md). ### Cart PHP API Reorder also facilitates `Ibexa\Contracts\Cart\CartServiceInterface` interface `mergeCarts` method. For more information on it, see [Cart API documentation](https://doc.ibexa.co/en/latest/commerce/cart/cart_api/#merge-carts). # Checkout API Editions: Commerce To get checkouts and manage them, use the `Ibexa\Contracts\Checkout\CheckoutServiceInterface` interface. With `CheckoutServiceInterface`, you manipulate checkouts that are stored in sessions. Checkouts are containers for the `Ibexa\Contracts\Cart\Value\CartInterface` object and all the data provided at each step of the [configurable checkout process](https://doc.ibexa.co/en/latest/commerce/checkout/configure_checkout/index.md). The checkout process relies on Symfony Workflow, and you can customize each of its steps. Each checkout step has its own controller that allows adding forms and external API calls that process data and pass them to `CheckoutService`. Completing a step results in submitting a form and updating the current checkout object. At this point Symfony Workflow advances, the next controller takes over, and the whole process continues. From the developer's perspective, checkouts are referenced with an UUID identifier. ## Get single checkout by identifier To access a single checkout, use the `CheckoutServiceInterface::getCheckout` method: ``` $checkout = $this->checkoutService->getCheckout($checkoutIdentifier); ``` ## Get single checkout for specific cart To fetch checkout for a cart that already exists, use the `CheckoutServiceInterface::getCheckoutForCart` method. You can use it when you want to initiate the checkout process right after products are successfully added to a cart. ``` $cart = $this->cartService->getCart('d7424b64-7dc1-474c-82c8-1700f860d55e'); $checkoutForCart = $this->checkoutService->getCheckoutForCart($cart); $checkoutIdentifier = $checkoutForCart->getIdentifier(); ``` ## Create checkout To create a checkout, use the `CheckoutServiceInterface::createCheckout` method and provide it with a `CheckoutCreateStruct` struct that contains a `CartInterface` object. ``` $newCart = $this->cartService->getCart('1844450e-61da-4814-8d82-9301a3df0054'); $checkoutCreateStruct = $this->checkoutService->newCheckoutCreateStruct($newCart); $newCheckout = $this->checkoutService->createCheckout($checkoutCreateStruct); $newCheckoutIdentifier = $newCheckout->getIdentifier(); ``` ## Update checkout You can update the collected data after the checkout is created. The data is stored within the `CheckoutInterface::context` object. The last update time and status are also stored. To update the checkout, use the `CheckoutServiceInterface::updateCheckout` method and provide it with the `CheckoutUpdateStruct` struct that contains data collected at each step of the workflow, and a transition name to identify what step follows. All data is placed in session storage. ``` $checkoutUpdateStruct = $this->checkoutService->newCheckoutUpdateStruct('select_address'); $this->checkoutService->updateCheckout($newCheckout, $checkoutUpdateStruct); ``` ## Delete checkout To delete a checkout from the session, use the `CheckoutServiceInterface::deleteCheckout` method: ``` $this->checkoutService->deleteCheckout($newCheckout); ``` # Order management Editions: Commerce The order management component enables users to search for orders and filter search results. Depending on their role, users can also track the status of their orders, review order details, and cancel orders. From the development perspective, the component enables customization of the order management workflow and integration with external systems to exchange order information. The component exposes the following: - [PHP API](https://doc.ibexa.co/en/latest/commerce/order_management/order_management_api/index.md) that allows for managing orders - [REST API](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Orders) that helps get order information over HTTP ### Order management service The Order Management package provides the `Ibexa\Contracts\OrderManagement\OrderServiceInterface` service, which is the entrypoint for calling the [backend API](https://doc.ibexa.co/en/latest/commerce/order_management/order_management_api/index.md). # Configure order processing Editions: Commerce When you work with your Commerce implementation, you can modify and customize the order processing configuration. > **Note: Permissions** > > When you modify the workflow configuration, make sure you properly set user [permissions](https://doc.ibexa.co/en/latest/permissions/permission_use_cases/#commerce) for the Order management component. ## Configure order processing workflow Order processing workflow relies on a [Symfony Workflow](https://symfony.com/doc/7.4/components/workflow.html). Each transition represents a separate order processing step. ### Default order processing configuration The default order processing workflow is called `ibexa_order`. To see the default workflow configuration, in your project directory, go to: `vendor/ibexa/order-management/src/bundle/Resources/config/prepend.yaml`. The default workflow uses keys defined in `Ibexa\OrderManagement\Value\Status` class as place and transition names, for example, `PENDING_PLACE` translates into `pending`. You can replace the default workflow configuration with a custom one if needed. ### Custom order processing workflows You define custom workflow implementations under the `framework.workflows` key. If your installation supports multiple languages, for each place in the workflow, you can define a label that is pulled from a XLIFF file based on the [translation domain setting](https://doc.ibexa.co/en/latest/multisite/languages/back_office_translations/index.md). You can also define colors that are used for status labels. To customize your configuration, place it under the `framework.workflows.` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` framework: workflows: custom_order_workflow: type: state_machine audit_trail: enabled: "%kernel.debug%" marking_store: type: method property: status supports: - Ibexa\Contracts\OrderManagement\Workflow\WorkflowSubjectInterface initial_marking: created places: created: metadata: reduce_stock: true label: 'order.status.label.created' translation_domain: 'order_management' primary_color: '#F4B65F' secondary_color: '#FEEED9' verified: metadata: label: 'order.status.label.verified' translation_domain: 'order_management' primary_color: '#2B6875' secondary_color: '#CCDBDE' in_progress: metadata: label: 'order.status.label.in_progress' translation_domain: 'order_management' primary_color: '#FF9071' secondary_color: '#FFDACF' completed: metadata: label: 'order.status.label.completed' translation_domain: 'order_management' primary_color: '#33B655' secondary_color: '#E5F5E9' dropped: metadata: restore_stock: true label: 'order.status.label.dropped' translation_domain: 'order_management' primary_color: '#5A5A5D' secondary_color: '#E6E6ED' transitions: verify: from: - created to: - verified advance: from: - verified to: - in_progress finish: from: - in_progress to: - completed drop: from: - created to: - dropped ``` Then reference it with `ibexa.repositories..order_management.workflow: `, so that the system can identify which of your configured workflows handles the ordering process. ``` ibexa: repositories: default: order_management: workflow: custom_order_workflow ``` ### Define cancel order You can define a status and transition in which the order can be canceled by modifying workflow under the `framework.workflows` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files). ``` framework: workflows: ibexa_order: metadata: cancel_status: !php/const Ibexa\OrderManagement\Value\Status::CANCELLED_PLACE cancel_transition: !php/const Ibexa\OrderManagement\Value\Status::CANCEL_TRANSITION ``` ### PIM integration By default, the component integration mechanism reduces product stock values when an order is made (in status "pending") and reverts it to the original value when an order is cancelled. In your implementation, you may want the reduction/restoration of stock to happen at other stages of the order fulfillment process. For this to happen, place the `reduce_stock: true` and/or `restore_stock: true` keys in other places of the workflow. Make sure that either of these keys is used only once. # Order management API Editions: Commerce > **Tip: Order management REST API** > > To learn how to manage orders with the REST API, see the [REST API reference](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Orders). To get orders and manage them, use the [`Ibexa\Contracts\OrderManagement\OrderServiceInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-OrderManagement-OrderServiceInterface.html) interface. ## Get single order ### Get single order by identifier To access a single order by using its string identifier, use the [`OrderServiceInterface::getOrderByIdentifier`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-OrderManagement-OrderServiceInterface.html#method_getOrderByIdentifier) method: ``` $orderIdentifier = '2e897b31-0d7a-46d3-ba45-4eb65fe02790'; $order = $this->orderService->getOrderByIdentifier($orderIdentifier); $output->writeln(sprintf('Order %s has status %s', $orderIdentifier, $order->getStatus())); ``` Use the returned [`OrderInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-OrderManagement-Value-Order-OrderInterface.html) value object to access details about the order. See the [Discounts API](https://doc.ibexa.co/en/latest/discounts/discounts_api/#retrieve-applied-discounts) to learn how to retrieve applied discount details from the order's context. ### Get single order by ID To access a single order by using its numerical ID, use the [`OrderServiceInterface::getOrder`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-OrderManagement-OrderServiceInterface.html#method_getOrder) method: ``` $orderId = 1; $order = $this->orderService->getOrder($orderId); $output->writeln(sprintf('Order %d has status %s', $orderId, $order->getStatus())); ``` ## Get multiple orders To fetch multiple orders, use the [`OrderServiceInterface::findOrders`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-OrderManagement-OrderServiceInterface.html#method_findOrders) method. It follows the same search query pattern as other APIs: ``` use Ibexa\Contracts\CoreSearch\Values\Query\Criterion\LogicalOr; use Ibexa\Contracts\OrderManagement\Value\Order\OrderQuery; use Ibexa\Contracts\OrderManagement\Value\Order\Query\Criterion\CompanyNameCriterion; use Ibexa\Contracts\OrderManagement\Value\Order\Query\Criterion\CustomerNameCriterion; use Ibexa\Contracts\OrderManagement\Value\Order\Query\Criterion\IdentifierCriterion; // ... $orderCriterions = [ new IdentifierCriterion('c328773e-8daa-4465-86d5-4d7890f3aa86'), new CompanyNameCriterion('IBM'), new CustomerNameCriterion('foo_user'), ]; $orderQuery = new OrderQuery(new LogicalOr(...$orderCriterions)); $orders = $this->orderService->findOrders($orderQuery); $output->writeln(sprintf('Found %d orders with provided criteria', count($orders))); ``` ## Create order To create an order, use the [`OrderServiceInterface::createOrder`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-OrderManagement-OrderServiceInterface.html#method_createOrder) method and provide it with the [`Ibexa\Contracts\OrderManagement\Value\Struct\OrderCreateStruct`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-OrderManagement-Value-Struct-OrderCreateStruct.html) object that contains a list of products, purchased quantities, product, total prices, and tax amounts. ``` $orderCreateStruct = new OrderCreateStruct( $user, $currency, $value, 'local_shop', $items ); $order = $this->orderService->createOrder($orderCreateStruct); $output->writeln(sprintf('Created order with identifier %s', $order->getIdentifier())); ``` ## Update order You can update the order after it's created. You could do it to support a scenario when, for example, the order is processed manually and its status has to be changed in the system. To update order information, use the [`OrderServiceInterface::updateOrder`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-OrderManagement-OrderServiceInterface.html#method_updateOrder) method: ``` $orderUpdateStruct = new OrderUpdateStruct('processed'); $this->orderService->updateOrder($order, $orderUpdateStruct); $output->writeln(sprintf('Changed order status to %s', $order->getStatus())); ``` # Payment Editions: Commerce With the Payment component users can define and manage payment methods, create and manage payments, search for payment methods and payments, and filter payment search results. Depending on their role, users can also enable or disable payment methods, modify payment information, and cancel payments. Available payment method types: - offline – out of the box - online payment services – through [integration with Payum](https://doc.ibexa.co/en/latest/commerce/payment/payum_integration/index.md) From the development perspective, the component enables [customization of the payment workflow](https://doc.ibexa.co/en/latest/commerce/payment/configure_payment/#custom-payment-workflows). The component exposes the following APIs: - [Payment method PHP API](https://doc.ibexa.co/en/latest/commerce/payment/payment_method_api/index.md) that allows for managing payment methods - [Payment method REST API](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Payments) that helps manage payment methods over HTTP - [Payment PHP API](https://doc.ibexa.co/en/latest/commerce/payment/payment_api/index.md) that allows for managing payments ### Services The Payment package provides the following services, which are entry points for calling backend APIs: - `Ibexa\Contracts\Payment\PaymentMethodServiceInterface` - `Ibexa\Contracts\Payment\PaymentServiceInterface` # Configure payment Editions: Commerce When you work with your Commerce implementation, you can review and modify the payment configuration. > **Note: Permissions** > > When you modify the workflow configuration, make sure you properly set user [permissions](https://doc.ibexa.co/en/latest/permissions/permission_use_cases/#commerce) for the Payment component. ## Configure payment workflow Payment workflow relies on a [Symfony Workflow](https://symfony.com/doc/7.4/components/workflow.html). Each transition represents a separate payment step. ### Default payment workflow configuration The default payment workflow is called `ibexa_payment`. To see the default workflow configuration, in your project directory, go to: `vendor/ibexa/payment/src/bundle/Resources/config/prepend.yaml`. You can replace the default workflow configuration with a custom one if needed. ### Custom payment workflows You define custom workflow implementations under the `framework.workflows` key. They must support the `Ibexa\Contracts\Checkout\Value\CheckoutInterface`. If your installation supports multiple languages, for each place in the workflow, you can define a label that is pulled from an XLIFF file based on the translation domain setting. You can also define colors that are used for status labels. The `primary_color` key defines a color of the font used for the label, while the `secondary_color` key defines a color of its background. Additionally, you can decide whether users can manually transition between places. You do this by setting a value for the `exposed` key. If you set it to `true`, a button is displayed in the UI that triggers the transition. Otherwise, the transition can only be triggered by means of the API. ``` framework: workflows: custom_payment_workflow: type: state_machine audit_trail: enabled: "%kernel.debug%" marking_store: type: method property: status supports: - Ibexa\Contracts\Payment\Payment\Workflow\WorkflowSubjectInterface initial_marking: open places: open: metadata: label: ibexa.payment.workflow.place.open.label primary_color: '#F4B65F' secondary_color: '#FEEED9' translation_domain: ibexa_payment_workflow paid: metadata: label: ibexa.payment.workflow.place.paid.label primary_color: '#2B6875' secondary_color: '#CCDBDE' translation_domain: ibexa_payment_workflow transitions: pay: from: - open to: paid metadata: exposed: false ``` After you configure a custom workflow, reference it under the `ibexa.repositories..payment.workflow` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files), so that the system can identify which of your workflows handles the payment process. ``` ibexa: repositories: default: payment: workflow: custom_payment_workflow ``` ## Configure payment methods You can define payment methods [in the UI](https://doc.ibexa.co/projects/userguide/en/5.0/commerce/payment/work_with_payment_methods/). There is only one default payment method type available: `offline`, but you can configure more by [integrating with Payum](https://doc.ibexa.co/en/latest/commerce/payment/payum_integration/index.md), or [add custom ones](https://doc.ibexa.co/en/latest/commerce/payment/extend_payment/index.md). # Extend Payment Editions: Commerce You can extend your Payment module implementation: - by creating a custom payment method type - by attaching custom data to a payment You can also [customize the payment processing workflow](https://doc.ibexa.co/en/latest/commerce/payment/configure_payment/#custom-payment-workflows). ## Create custom payment method type If your application needs payment methods of other type than the default `offline` one, or ones offered by Payum, you can create custom payment method types. Code samples below show how this could be done if your organization wants to use PayPal independently. > **Note: Gateway integration requirement** > > Ibexa DXP doesn't come with gateway redirects. Whether you're an integrator or an end customer, it's your responsibility to implement payment gateway integration. ### Define custom payment method type Create a PHP definition of the payment method type. ``` add('base_url', UrlType::class, [ 'constraints' => [new NotBlank()], ]); // ... } } ``` Next, create a mapper that maps the information that the user inputs in the form into attribute definition. ``` add($name, PayPalOptionsType::class); } } ``` Then, register `OptionsFormMapper` as a service: ``` services: App\Form\Type\OptionsFormMapper: tags: - name: ibexa.payment.payment_method.options.form_mapper type: paypal ``` ### Create options validator You might want to make sure that data provided by the user is validated. To do that, create an options validator that checks user input against the constraints and dispatches an error when needed. ``` get('base_url'))) { $errors[] = new OptionsValidatorError('base_url', 'Base URL cannot be blank'); } // Add gateway implementation here return $errors; } } ``` Then, register the validator as a service: ``` services: App\Form\Type\OptionsValidator: tags: - name: ibexa.payment.payment_method.options.validator type: paypal ``` ### Restart application Shut down the application, clear browser cache, and restart the application. Then, try creating a payment of the new type. *[Image: Payment method of custom type]* ## Attach custom data to payments When you create a payment, you can attach custom data to it, for example, you can pass an invoice number or a proprietary transaction identifier. You add custom data by using the `setContext` method: ``` $context = [ 'transaction_id' => '5e5fe187-c865-49£2-b407-a946fd7b5be0', ]; $paymentCreateStruct = new PaymentCreateStruct( $this->paymentMethodService->getPaymentMethodByIdentifier('bank_transfer_EUR'), $this->orderService->getOrder(135), new Money\Money(100, new Money\Currency('EUR')) ); $paymentCreateStruct->setContext(new ArrayMap($context)); $payment = $this->paymentService->createPayment($paymentCreateStruct); ``` Then, you retrieve it with the `getContext` method: ``` $paymentIdentifier = '4ac4b8a0-eed8-496d-87d9-32a960a10629'; $payment = $this->paymentService->getPaymentByIdentifier($paymentIdentifier); $context = $payment->getContext(); ``` # Payment method API Editions: Commerce > **Tip: Order management REST API** > > To learn how to manage payment methods with the REST API, see the [REST API reference](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Payments). To get payment methods and manage them, use the `Ibexa\Contracts\Payment\PaymentMethodServiceInterface` interface. From the developer's perspective, payment methods are referenced with identifiers defined manually at method creation stage in user interface. > **Note: Support for multilingual applications** > > The `getPaymentMethodByIdentifier`, `getPaymentMethod` and `findPaymentMethods` methods take a second argument, `$prioritizedLanguages`, that can be an array of language codes or `null`. If there are language codes in an array, methods return payment method name translations in the specified languages. Translations come from the database. ## Get single payment method ### Get single payment method by identifier To access a single payment method by using its string identifier, use the `PaymentMethodService::getPaymentMethodByIdentifier` method: ``` $paymentMethodIdentifier = 'cash'; $paymentMethod = $this->paymentMethodService->getPaymentMethodByIdentifier($paymentMethodIdentifier); $output->writeln(sprintf('Availability status of payment method "%s" is "%s".', $paymentMethodIdentifier, $paymentMethod->isEnabled())); ``` ### Get single payment method by ID To access a single payment method by using its numerical ID, use the `PaymentMethodService::getPaymentMethod` method: ``` $paymentMethodId = 1; $paymentMethod = $this->paymentMethodService->getPaymentMethod($paymentMethodId); $output->writeln(sprintf('Payment method %d has type "%s"', $paymentMethodId, $paymentMethod->getType()->getIdentifier())); ``` ## Get multiple payment methods To fetch multiple payment methods, use the `PaymentMethodService::findPaymentMethods` method. It follows the same search query pattern as other APIs: ``` $offlinePaymentType = new PaymentMethodType('offline', 'Offline'); $paymentMethodCriterions = [ new Type($offlinePaymentType), new Enabled(true), ]; $paymentMethodQuery = new PaymentMethodQuery((new LogicalAnd(...$paymentMethodCriterions))); $paymentMethodQuery->setLimit(10); $paymentMethods = $this->paymentMethodService->findPaymentMethods($paymentMethodQuery); $paymentMethods->getPaymentMethods(); $paymentMethods->getTotalCount(); foreach ($paymentMethods as $paymentMethod) { $output->writeln($paymentMethod->getIdentifier() . ': ' . $paymentMethod->getName() . ' - ' . $paymentMethod->getDescription()); } ``` ## Create payment method To create a payment method, use the `PaymentMethodService::createPaymentMethod` method and provide it with an `Ibexa\Contracts\Payment\PaymentMethod\PaymentMethodCreateStruct` object that takes the following parameters: - `identifier` string - `type` TypeInterface object - `names` array of string values - `descriptions` array of string values - `enabled` boolean value - `options` object. ``` $offlinePaymentType = new PaymentMethodType('offline', 'Offline'); $paymentMethodCreateStruct = new PaymentMethodCreateStruct( 'bank_transfer_EUR', $offlinePaymentType, ); $paymentMethodCreateStruct->setName('eng-GB', 'Bank transfer EUR'); $paymentMethodCreateStruct->setEnabled(false); $paymentMethod = $this->paymentMethodService->createPaymentMethod($paymentMethodCreateStruct); $output->writeln(sprintf('Created payment method with name %s', $paymentMethod->getName())); ``` ## Update payment method You can update the payment method after it's created. An `Ibexa\Contracts\Payment\PaymentMethod\PaymentMethodUpdateStruct` object can take the following arguments: `identifier` string, `names` array of string values, `descriptions` array of string values, `enabled` boolean value, and an `options` object. To update payment method information, use the `PaymentMethodServiceInterface::updatePaymentMethod` method: ``` $paymentMethodUpdateStruct = new PaymentMethodUpdateStruct(); $paymentMethodUpdateStruct->setEnabled(true); $this->paymentMethodService->updatePaymentMethod($paymentMethod, $paymentMethodUpdateStruct); $output->writeln(sprintf( 'Updated payment method "%s" by changing its availability status to "%s".', $paymentMethod->getName(), $paymentMethod->isEnabled() )); ``` ## Delete payment method To delete a payment method from the system, use the `PaymentMethodService::deletePayment` method: ``` $this->paymentMethodService->deletePaymentMethod($paymentMethod); $output->writeln(sprintf( 'Deleted payment method with ID %d and identifier "%s".', $paymentMethod->getId(), $paymentMethod->getIdentifier() )); ``` ## Check whether payment method is used To check whether a payment method is used, for example, before you delete it, use the `PaymentMethodService::isPaymentMethodUsed` method: ``` $isUsed = $this->paymentMethodService->isPaymentMethodUsed($paymentMethod); if ($isUsed) { $output->writeln(sprintf( 'Payment method with ID %d is currently used.', $paymentMethod->getId() )); } else { $output->writeln(sprintf( 'Payment method with ID %d is not used.', $paymentMethod->getId() )); } ``` # Implement payment method filtering Editions: Commerce You can use payment method filtering to decide, whether selected payment method can be used and displayed in checkout process. To allow this filtering, you need to create a custom payment method type and register new voter. ## Create custom payment method type You can extend your Payment module implementation in different ways. One of them is to [create a custom payment method type](https://doc.ibexa.co/en/latest/commerce/payment/extend_payment/index.md). The following example shows, how to create `New Payment Method Type`. ### Define custom payment method type First, register the new payment method type as a service: ``` services: app.payment.type.new_payment_method_type: class: Ibexa\Contracts\Payment\PaymentMethod\Type\TypeInterface factory: ['@Ibexa\Contracts\Payment\PaymentMethod\Type\TypeFactoryInterface', 'createType'] arguments: $identifier: new_payment_method_type $name: New Payment Method Type $domain: tags: - { name: ibexa.payment.payment_method.type, alias: new_payment_method_type } ``` In the `arguments` list provide a name of the payment method type, the way you want it to appear on the list of available payment method types, in the following example: `New Payment Method Type`. Now new custom payment method type should be visible in **Commerce** -> **Payment methods**. ### Create voter for new payment method type Next, create a `NewPaymentMethodTypeVoter.php` file with the voter definition for your new payment method type: ``` paymentService->getPayment($paymentId); $output->writeln(sprintf('Payment %d has status %s', $paymentId, $payment->getStatus())); ``` ### Get single payment by identifier To access a single payment by using its string identifier, use the `PaymentServiceInterface::getPaymentByIdentifier` method: ``` $paymentIdentifier = '4ac4b8a0-eed8-496d-87d9-32a960a10629'; $payment = $this->paymentService->getPaymentByIdentifier($paymentIdentifier); ``` ## Get multiple payments To fetch multiple payments, use the `PaymentServiceInterface::findPayments` method. It follows the same search query pattern as other APIs: ``` $paymentCriterions = [ new Currency('USD'), new Currency('CZK'), ]; $paymentQuery = new PaymentQuery(new LogicalOr(...$paymentCriterions)); $paymentQuery->setLimit(10); $paymentsList = $this->paymentService->findPayments($paymentQuery); $paymentsList->getPayments(); $paymentsList->getTotalCount(); foreach ($paymentsList as $payment) { $output->writeln($payment->getIdentifier() . ': ' . $payment->getOrder()->getIdentifier() . ': ' . $payment->getOrder()->getValue()->getTotalGross()->getAmount()); } ``` ## Create payment To create a payment, use the `PaymentServiceInterface::createPayment` method and provide it with the `Ibexa\Contracts\Payment\Payment\PaymentCreateStruct` object that takes the following arguments: `method`, `order` and `amount`. ``` $context = [ 'transaction_id' => '5e5fe187-c865-49£2-b407-a946fd7b5be0', ]; $paymentCreateStruct = new PaymentCreateStruct( $this->paymentMethodService->getPaymentMethodByIdentifier('bank_transfer_EUR'), $this->orderService->getOrder(135), new Money\Money(100, new Money\Currency('EUR')) ); $paymentCreateStruct->setContext(new ArrayMap($context)); $payment = $this->paymentService->createPayment($paymentCreateStruct); $output->writeln(sprintf('Created payment %s for order %s', $payment->getIdentifier(), $payment->getOrder()->getIdentifier())); ``` ## Update payment You can update payment information after the payment is created. You could do it to support a scenario when, for example, an online payment failed, has been processed by using other means, and its status has to be updated in the system. The `Ibexa\Contracts\Payment\Payment\PaymentUpdateStruct` object takes the following arguments: `transition`, `identifier`, and `context`. To update payment information, use the `PaymentServiceInterface::updatePayment` method: ``` $paymentUpdateStruct = new PaymentUpdateStruct(); $paymentUpdateStruct->setTransition('pay'); $this->paymentService->updatePayment($payment, $paymentUpdateStruct); $output->writeln(sprintf('Changed payment status to %s', $payment->getStatus())); ``` ## Delete payment To delete a payment from the system, use the `PaymentServiceInterface::deletePayment` method: ``` $this->paymentService->deletePayment($payment); ``` # Payum integration Editions: Commerce [Payum](https://payum.gitbook.io/payum) is a payment processing solution that simplifies the integration of various payment services like Stripe and PayPal into your application. These services provide security of online transactions, and allow you to accept multiple payment methods while ensuring a seamless experience for the customers. By configuring service gateways, mapping workflow actions and translating payment service names, you streamline the online payment process, and can offer a diverse payment experience. ## General Payum configuration In your Payum configuration file, for example, `payum.yaml`, set up a payment service gateway by specifying the factory, credentials and other necessary settings. Replace `` with a unique identifier of the method provided by the payment service. ``` payum: gateways: : factory: # Add specific configuration fields for the gateway credential_1: credential_2: ``` ## Workflow mapping In Ibexa DXP, the default payment workflow has certain places, such as `pending`, `failed`, `paid`, or `cancelled`, and their corresponding transitions. However, for your application to use other transitions and places, for example, `authorized`, `notified`, or `refunded`, and to present them in the user interface, you need to: - override the default payment workflow - create a custom workflow and enable it by using semantic configuration For more information, see [Custom payment workflows](https://doc.ibexa.co/en/latest/commerce/payment/configure_payment/#custom-payment-workflows). For these places to be supported by the Payum integration, you have to map Payum statuses on the existing or additional places in the workflow, for example: ``` ibexa_connector_payum: status_mapping: refunded: cancelled captured: pending authorized: authorized [...] ``` ## Payment service name translations Within the `ibexa_payment_type` namespace in your translation files, add translations for each payment service that you configure. For language translations of payment service names, structure the translation files as follows: ``` ibexa: payment_method: type: : name: "Translated payment service name" ``` > **Note: Note** > > Replace `` with the identifier used in the Payum configuration. ## Implementation When you implement the online payment solution, take the following consideration into account: - To learn what credentials must be provided and what specific settings must be made, refer to the each payment service gateway's specific documentation. - To customize the online payment UI, see [Creating custom views](https://github.com/Payum/Payum/blob/master/docs/symfony/custom-payment-page.md) in Payum documentation. - When you modify the payment process, you may need to subscribe to events dispatched by Payum. For a list of events, see [Event dispatcher](https://github.com/Payum/Payum/blob/master/docs/event-dispatcher.md) in Payum documentation. > **Caution: Caution** > > In certain cases, depending on the payment processing service, when a customer closes the payment page in a browser and the bank has not processed the payment yet, the payment status can remain unchanged. Depending on how your checkout process is configured, it may result in unwanted effects, for example, cause that the cart doesn't purge after the purchase. Make sure that you account for this fact in your implementation. Editions: Commerce ### Enable PayPal payments with Payum By using Payum to integrate PayPal into your application, you can offer your customers a versatile payment processing service that supports various payment methods, including credit cards, debit cards, Pay Later options, and alternative payment methods. Before you can proceed with integrating PayPal, you must [create a PayPal business account](https://www.paypal.com/bizsignup/#/singlePageSignup) and obtain API credentials. Install the PayPal package and the required dependencies: ``` composer require payum/paypal-express-checkout-nvp php-http/guzzle7-adapter php-http/message php-http/message-factory ``` Then, add the following configuration to your YAML configuration file (`payum.yaml` or similar): ``` payum: gateways: pp_express_checkout: factory: paypal_express_checkout username: password: signature: ``` > **Tip: Tip** > > You can replace `pp_express_checkout` with a different unique identifier. Ensure that the `username`, `password,` and `signature` fields contain the PayPal API credentials obtained from your PayPal business account. You can now provide language translations for the PayPal payment service name. To do it, within the `ibexa_payment_type` namespace in your translation files, use the provided translation key structure for each of your supported languages: ``` ibexa: payment_method: type: pp_express_checkout: name: "Translated PayPal Express Checkout name" ``` # Enable Stripe payments with Payum Editions: Commerce Stripe is a comprehensive payment platform that offers a suite of tools to handle online and in-person payments, subscriptions, fraud prevention, and more. By using Payum to integrate Stripe into your application, you can securely process payments with credit cards, bank transfers, and alternative payment methods. Before you can proceed with integrating Stripe, [sign up for a Stripe account](https://dashboard.stripe.com/register) and obtain the API keys required for integration. Install the Stripe package and the required dependencies: ``` composer require payum/stripe php-http/guzzle7-adapter php-http/message php-http/message-factory ``` Then, add the following configuration to your YAML configuration file (`payum.yaml` or similar): ``` payum: gateways: strp_checkout: factory: stripe_checkout publishable_key: secret_key: ``` > **Tip: Tip** > > You can replace `strp_checkout` with a different unique identifier. Ensure that the `publishable_key` and `secret_key` fields contain the Stripe API keys. You can now provide language translations for the Stripe payment platform name. To do it, within the `ibexa_payment_type` namespace in your translation files, use the provided translation key structure within your translation files: ``` ibexa: payment_method: type: strp_checkout: name: "Translated Stripe Checkout name" ``` # Shipping Editions: Commerce The shipping component enables users to define and manage shipping methods of different types, create and manage shipments, search for shipments, and filter search results. Depending on their role, users can also enable or disable shipping methods, change status of shipments, and cancel shipments. > **Note: Shipping method types** > > Two types of shipping methods are available by default: `flat rate` and `free`. From the development perspective, the component enables customization of the shipment workflow. The component exposes the following: - [Shipping method PHP API](https://doc.ibexa.co/en/latest/commerce/shipping_management/shipping_method_api/index.md) that allows for managing shipping methods - [Shipment PHP API](https://doc.ibexa.co/en/latest/commerce/shipping_management/shipment_api/index.md) that allows for managing shipments ### Services The Shipping package provides the following services, which are entry points for calling backend APIs: - `Ibexa\Contracts\Shipping\ShippingMethodServiceInterface` - `Ibexa\Contracts\Shipping\ShipmentServiceInterface` # Configure shipping Editions: Commerce When you work with your Commerce implementation, you can review and modify the shipping configuration. > **Note: Permissions** > > When you modify the workflow configuration, make sure you properly set user [permissions](https://doc.ibexa.co/en/latest/permissions/permission_use_cases/#commerce) for the shipping component. ## Configure shipment workflow Shipment workflow relies on a [Symfony Workflow](https://symfony.com/doc/7.4/components/workflow.html). Each transition represents a separate shipment step. The default fallback workflow is `ibexa_shipment`, which is prepended at bundle level. ### Default shipment workflow configuration The default payment workflow configuration is called `ibexa_shipment`, you can replace it with your custom workflow identifier if needed. To see the default workflow, in your project directory, navigate to the following file: `vendor/ibexa/shipping/src/bundle/Resources/config/workflow.yaml`. ### Custom shipment workflows You define custom workflow implementations under the `framework.workflows` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files). The `shipping.shipment_workflow` parameter is repository-aware. To customize your configuration, place it under the `framework.workflows.` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` framework: workflows: custom_shipment_workflow: type: state_machine audit_trail: enabled: "%kernel.debug%" marking_store: type: method property: status supports: - Ibexa\Contracts\Shipping\Shipment\ShipmentInterface - Ibexa\Contracts\Shipping\Shipment\ShipmentCreateStruct initial_marking: pending places: pending: metadata: label: ibexa.shipment.workflow.place.pending.label primary_color: '#F4B65F' secondary_color: '#FEEED9' translation_domain: ibexa_shipment_workflow ready_for_clearance: metadata: label: ibexa.shipment.workflow.place.ready_for_clearance.label primary_color: '#DB2C54' secondary_color: '#F7CCD6' translation_domain: ibexa_shipment_workflow in_customs: metadata: label: ibexa.shipment.workflow.place.in_customs.label primary_color: '#DB2C54' secondary_color: '#F7CCD6' translation_domain: ibexa_shipment_workflow passed_customs_clearance: metadata: label: ibexa.shipment.workflow.place.passed_customs_clearance.label primary_color: '#1beb17' secondary_color: '#c5f2c4' translation_domain: ibexa_shipment_workflow shipped: metadata: label: ibexa.shipment.workflow.place.shipped.label primary_color: '#5A5A5D' secondary_color: '#E6E6ED' translation_domain: ibexa_shipment_workflow delivered: metadata: label: ibexa.shipment.workflow.place.delivered.label primary_color: '#2B6875' secondary_color: '#CCDBDE' translation_domain: ibexa_shipment_workflow transitions: prepare: from: - pending to: ready_for_clearance metadata: exposed: false sent_to_customs: from: - ready_for_clearance to: in_customs metadata: exposed: false clear_at_customs: from: - in_customs to: passed_customs_clearance metadata: exposed: false send: from: - passed_customs_clearance to: shipped metadata: exposed: false deliver: from: - shipped to: delivered metadata: exposed: false ``` Reference it with `ibexa.repositories..shipment.workflow: your_workflow_name`, so that the system can then identify which of your configured workflows handles the shipment process. ``` ibexa: repositories: default: shipping: shipment_workflow: custom_shipment_workflow ``` ## Configure shipping methods You can define the shipping methods [in the UI](https://doc.ibexa.co/projects/userguide/en/5.0/commerce/shipping_management/work_with_shipping_methods/). The following shipping method types are available by default: `flat rate` and `free`. # Extend shipping Editions: Commerce You can extend or customize your Shipping module implementation in different ways. Here, you can learn about the following ideas to make your Commerce solution more powerful: - create a custom shipping method type - toggle shipping method availability in checkout based on a condition - display shipping method parameters on the shipping method details page You can also [customize the shipment processing workflow](https://doc.ibexa.co/en/latest/commerce/shipping_management/configure_shipment/#custom-shipment-workflows). ## Create custom shipping method type If your application needs shipping methods of other type than the default ones, you can create custom shipping method types. See the code samples below to learn how to do it. ### Define custom shipping method type class Create a definition of the shipping method type. Use a built-in type factory to define the class in `config/services.yaml`: ``` services: app.shipping.shipping_method_type.custom: class: Ibexa\Shipping\ShippingMethod\ShippingMethodType arguments: $identifier: 'custom' tags: - name: ibexa.shipping.shipping_method_type alias: custom ``` At this point a custom shipping method type should be visible on the **Create shipping method** modal, the **Method type** list. *[Image: Selecting a shipping method type]* ### Create options form To let users create shipping methods of a custom type within the user interface, you need a Symfony form type. Create a `src/ShippingMethodType/Form/Type/CustomShippingMethodOptionsType.php` file with a form type. Next, define a name of the custom shipping method type in the file, by using the `getTranslationMessages` method. ``` add('customer_identifier', TextType::class); } public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults(['translation_mode' => false]); $resolver->setAllowedTypes('translation_mode', 'bool'); } public static function getTranslationMessages(): array { return [ Message::create('ibexa.shipping_types.custom.name', 'ibexa_shipping')->setDesc('Custom'), ]; } } ``` Create a translations file `translations/ibexa_shipping.en.yaml` that stores a name value for the custom shipping method type: ``` ibexa.shipping_types.custom.name: 'Custom Type' ``` Next, use the type factory to define an options form mapper class in `config/services.yaml`: ``` services: app.shipping.shipping_method.custom.form_mapper.options: class: Ibexa\Bundle\Shipping\Form\ShippingMethod\OptionsFormMapper arguments: $formType: 'App\ShippingMethodType\Form\Type\CustomShippingMethodOptionsType' tags: - name: ibexa.shipping.shipping_method.form_mapper.options type: custom ``` At this point you should be able to create a shipping method based on a custom shipping method type. *[Image: Creating a shipping method of custom type]* > **Note: Note** > > To use this example, you must have regions. If you don't have regions, refer to [Enable purchasing products](https://doc.ibexa.co/en/latest/product_catalog/enable_purchasing_products/index.md) for instructions on how to add them. ### Create options validator You might want to validate the data provided by the user against certain constraints. Here, you create an options validator class that checks whether the user provided the `customer_identifier` value and dispatches an error when needed. Use the type factory to define a compound validator class in `config/services.yaml`: ``` services: app.shipping.shipping_method.options.custom_compound_validator: class: Ibexa\Shipping\Validation\Validator\CompoundValidator arguments: $validators: !tagged_iterator { tag: 'ibexa.shipping.shipping_method.options.validator.custom' } tags: - name: ibexa.shipping.shipping_method.options.validator type: custom ``` Then, create a `src/ShippingMethodType/CustomerNotNullValidator.php` file with a validator class: ``` get('customer_identifier'); if ($customerIdentifier === null) { return [ new OptionsValidatorError('[customer_identifier]', self::MESSAGE), ]; } return []; } public static function getTranslationMessages(): array { return [ Message::create(self::MESSAGE, 'validators')->setDesc('Customer identifier value cannot be null'), ]; } } ``` Finally, register the validator as a service: ``` services: App\ShippingMethodType\CustomerNotNullValidator: tags: - name: ibexa.shipping.shipping_method.options.validator.custom ``` Now, when you create a new shipping method and leave the **Customer identifier** field empty, you should see a warning. *[Image: Option validator in action]* ### Create storage converter Before form data can be stored in database tables, field values must be converted to a storage-specific format. Here, the storage converter converts the `customer_identifier` string value into the `customer_id` numerical value. Create a `src/ShippingMethodType/Storage/StorageConverter.php` file with a storage converter class: ``` $value['customer_identifier'], ]; } } ``` Then, register the storage converter as a service: ``` services: App\ShippingMethodType\Storage\StorageConverter: tags: - { name: 'ibexa.shipping.shipping_method.storage_converter', type: 'custom' } ``` #### Storage definition Now, create a storage definition class and a corresponding schema. The table stores information specific for the custom shipping method type. > **Note: Create table** > > Before you can proceed, in your database, create a table that has columns present in the storage definition, for example: > > `CREATE TABLE ibexa_shipping_method_region_custom(id int auto_increment primary key, customer_id text, shipping_method_region_id int);` Create a `src/ShippingMethodType/Storage/StorageDefinition.php` file with a storage definition: ``` Types::INTEGER, StorageSchema::COLUMN_CUSTOMER_ID => Types::STRING, ]; } public function getTableName(): string { return StorageSchema::TABLE_NAME; } } ``` Then, create a `src/ShippingMethodType/Storage/StorageSchema.php` file with a storage schema: ``` getOptions()->get('customer_identifier') === 'Acme'; } } ``` Register the voter as a service: ``` services: App\ShippingMethodType\Voter\CustomVoter: tags: - { name: ibexa.shipping.shipping.voter, method: custom } ``` ## Display shipping method parameters in details view You can extend the default shipping method details view by making shipping method visible on the **Cost** tab. To do this, create a `src/ShippingMethodType/Cost/CustomCostFormatter.php` file with a formatter class: ``` getOptions()->get('customer_identifier'); } } ``` Then register the formatter as a service: ``` services: App\ShippingMethodType\Cost\CustomCostFormatter: tags: - name: ibexa.shipping.shipping_method.formatter.cost type: custom ``` You should now see the parameter, in this case it's a customer identifier, displayed on the **Cost** tab of the shipping method's details view. *[Image: Shipping method parameters in the Cost tab]* > **Note: Non-matching label** > > This section doesn't discuss overriding the default form, therefore the alphanumerical customer identifier is shown under the **Cost value** label. For more information about working with forms, see [Page and Form tutorial](https://doc.ibexa.co/en/latest/tutorials/page_and_form_tutorial/5_create_newsletter_form/index.md). # Shipping method API Editions: Commerce To get shipping methods and manage them, use the `Ibexa\Contracts\Shipping\ShippingMethodServiceInterface` interface. Shipping methods are referenced with identifiers defined manually at method creation stage in user interface. ## Get shipping method ### Get shipping method by identifier To access a shipping method by using its identifier, use the `ShippingMethodServiceInterface::getShippingMethod` method. The method takes a string as `$identifier` parameter and uses a prioritized language from SiteAccess settings unless you pass another language as `forcedLanguage`. ``` $shippingMethodIdentifier = 'cash'; $shippingMethod = $this->shippingMethodService->getShippingMethod($shippingMethodIdentifier); $output->writeln( sprintf( 'Got shipping method by identifier "%s" and type "%s".', $shippingMethodIdentifier, $shippingMethod->getType()->getIdentifier() ) ); ``` ### Get shipping method by ID To access a shipping method by using its ID, use the `ShippingMethodServiceInterface::getShippingMethod` method. The method takes a string as `$id` parameter and uses a prioritized language from SiteAccess settings unless you pass another language as `forcedLanguage`. ``` $shippingMethodId = 1; $shippingMethod = $this->shippingMethodService->getShippingMethodById($shippingMethodId); $output->writeln( sprintf( 'Availability status of shipping method %d is "%s"', $shippingMethodId, $shippingMethod->isEnabled() ) ); ``` ## Get multiple shipping methods To fetch multiple shipping methods, use the `ShippingMethodServiceInterface::getShippingMethod` method. It follows the same search query pattern as other APIs: ``` $shippingMethodQuery = new ShippingMethodQuery(new ShippingMethodRegion($this->regionService->getRegion('default'))); $shippingMethodQuery->setLimit(10); $shippingMethods = $this->shippingMethodService->findShippingMethods($shippingMethodQuery); $shippingMethods->getShippingMethods(); $shippingMethods->getTotalCount(); foreach ($shippingMethods as $shippingMethod) { $output->writeln( sprintf( '%s: %s- %s', $shippingMethod->getIdentifier(), $shippingMethod->getName(), $shippingMethod->getDescription() ) ); } ``` ## Create shipping method To create a shipping method, use the `ShippingMethodServiceInterface::createShippingMethod` method and provide it with the `Ibexa\Contracts\Shipping\Value\ShippingMethodCreateStruct` object that you created by using the `newShippingMethodCreateStruct` method. ``` $shippingMethodCreateStruct = $this->shippingMethodService->newShippingMethodCreateStruct( 'courier', ); $shippingMethodCreateStruct->setType( new ShippingMethodType('flat_rate') ); $shippingMethodCreateStruct->setRegions(([new Region('default')])); $shippingMethodCreateStruct->setOptions( ['currency' => 1, 'price' => 1200] ); $shippingMethodCreateStruct->setVatCategoryIdentifier('standard'); $shippingMethodCreateStruct->setEnabled(true); $shippingMethodCreateStruct->setName('eng-GB', 'Courier'); $shippingMethod = $this->shippingMethodService->createShippingMethod($shippingMethodCreateStruct); $output->writeln( sprintf( 'Created shipping method with name %s', $shippingMethod->getName() ) ); ``` ## Update shipping method To update a shipping method, use the `ShippingMethodServiceInterface::updateShippingMethod` method and provide it with the `Ibexa\Contracts\Shipping\Value\ShippingMethodUpdateStruct` object that you created by using the `newShippingMethodUpdateStruct` method. ``` $shippingMethodUpdateStruct = $this->shippingMethodService->newShippingMethodUpdateStruct(); $shippingMethodUpdateStruct->setEnabled(false); $shippingMethodUpdateStruct->setOptions( ['currency' => 1, 'price' => 800] ); $shippingMethodUpdateStruct->setVatCategoryIdentifier('standard'); $shippingMethodUpdateStruct->setName('eng-GB', 'Courier'); $this->shippingMethodService->updateShippingMethod($shippingMethod, $shippingMethodUpdateStruct); $output->writeln(sprintf( 'Updated shipping method "%s"', $shippingMethod->getName(), )); ``` ## Delete shipping method To update a shipping method, use the `ShippingMethodServiceInterface::deleteShippingMethod` method. ``` $this->shippingMethodService->deleteShippingMethod($shippingMethod); $output->writeln(sprintf( 'Deleted shipping method with ID %d and identifier "%s".', $shippingMethod->getId(), $shippingMethod->getIdentifier() )); ``` ## Delete shipping method translation To delete shipping method translation, use the `ShippingMethodServiceInterface::deleteShippingMethodTranslation` method. ``` $languageCode = 'eng-GB'; $shippingMethodDeleteTranslationStruct = new ShippingMethodDeleteTranslationStruct($shippingMethod, $languageCode); $this->shippingMethodService->deleteShippingMethodTranslation($shippingMethodDeleteTranslationStruct); $output->writeln(sprintf( 'Deleted translation for shipping method "%s" and language "%s".', $shippingMethod->getName(), $languageCode )); ``` # Shipment API Editions: Commerce To get shipments and manage them, use the `Ibexa\Contracts\Shipping\ShipmentServiceInterface` interface. From the developer's perspective, shipments are referenced with a UUID identifier. ## Get single shipment ### Get single shipment by identifier To access a single shipment by using its string identifier, use the `ShipmentService::getShipmentByIdentifier` method: ``` $identifier = '4ac4b8a0-eed8-496d-87d9-32a960a10629'; $shipment = $this->shipmentService->getShipmentByIdentifier($identifier); $output->writeln( sprintf( 'Your shipment has status %s', $shipment->getStatus() ) ); ``` ### Get single shipment by id To access a single shipment by using its numerical id, use the `ShipmentService::getShipment` method: ``` $id = 1; $shipment = $this->shipmentService->getShipment($id); $output->writeln( sprintf( 'Shipment %d has status %s', $id, $shipment->getStatus() ) ); ``` ## Get multiple shipments To fetch multiple shipments, use the `ShipmentService::findShipments` method. It follows the same search query pattern as other APIs: ``` $shipmentCriteria = [ new ShippingMethod($this->shippingMethodService->getShippingMethod('free')), new CreatedAt(new \DateTime('2023-03-24 15:09:16')), new UpdatedAt(new \DateTime('2023-03-25 09:00:15')), ]; $shipmentQuery = new ShipmentQuery(new LogicalOr(...$shipmentCriteria)); $shipmentQuery->setLimit(20); $shipmentsList = $this->shipmentService->findShipments($shipmentQuery); $shipmentsList->getShipments(); $shipmentsList->getTotalCount(); foreach ($shipmentsList as $shipment) { $output->writeln( $shipment->getIdentifier() . ': ' . $shipment->getStatus() ); } ``` ## Create shipment To create a shipment, use the `ShipmentService::createShipment` method and provide it with an `Ibexa\Contracts\Shipping\Value\ShipmentCreateStruct` object that takes two parameters, a `shippingMethod` string and a `Money` object. ``` $shipmentCreateStruct = new ShipmentCreateStruct( $this->shippingMethodService->getShippingMethod('free'), $this->orderService->getOrder(135), new Money\Money(100, new Money\Currency('EUR')) ); $shipment = $this->shipmentService->createShipment($shipmentCreateStruct); $output->writeln( sprintf( 'Created shipment with identifier %s', $shipment->getIdentifier() ) ); ``` ## Update shipment You can update the shipment after it's created. You could do it to support a scenario when, for example, the shipment is processed offline and its status has to be updated in the system. To update shipment information, use the `ShipmentService::updateShipment` method: ``` $shipmentUpdateStruct = new ShipmentUpdateStruct(); $shipmentUpdateStruct->setTransition('send'); $this->shipmentService->updateShipment($shipment, $shipmentUpdateStruct); $output->writeln( sprintf( 'Changed shipment status to %s', $shipment->getStatus() ) ); ``` ## Delete shipment To delete a shipment from the system, use the `ShipmentService::deleteShipment` method: ``` $this->shipmentService->deleteShipment($shipment); ``` # Storefront Editions: Commerce The Storefront package provides a starting kit for the developers. It's a set of components that serves as a basis, which developers can customize and extend to create their own implementation of a web store. ## Default UI components The Storefront package contains the following default UI components and widgets. You can modify them when you build your own web store. | Component | Description | | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Cancel order | Allows logged-in users to cancel their orders in a pending status. | | Cart summary | Displays a subtotal net value of cart lines, a shipping cost disclaimer, a series of tax values applicable to products in cart, a composition of different taxes, and a total cart value (gross, shipping and taxes included). | | Checkout | Displays a series of screens that allow buyers to place an order for cart items. | | Currency menu | Enables selecting between currencies, to dynamically change the contents of the product listing page. | | Language menu | Enables selecting between languages, to change an active language. | | Login/register page | Provides user interface for the login/registration page that enables buyers to access the Product catalog. | | Main cart component | Main UI component of the cart. Displays a list of items selected for purchase and requested cart item quantities. Users can remove individual items. | | Mini cart widget | Consists of a counter that displays a total number of items added to a cart. | | Orders list | Displays a list of orders with such information as status, date, value, order ID. | | Product category page | Displays products that belong to a specific category. | | Product filters component | Allows for narrowing the list of products displayed in the listing by using different filters, such as product type, availability, and price. | | Product listing page | Allows for browsing through products, displays product name, code, price, and image. | | Region menu | Enables selecting between regions, to dynamically change the contents of the product listing page. | | Reorder | Allows logged-in users to repurchase previously bought items. | | Searching and filtering of orders | Allows logged-in users to search and filter their past orders on the orders page. | | Search for specific product component | Allows for searching for products, for example on the product listing page. | | Sort products component | Enables sorting products based on different criteria on a product listing page. | | Quick order | Enables buyer to provide or upload a list of products, with their quantities, intended for purchase. | To become familiar with a complete set of templates that covering all functionalities of a store, visit the `vendor/ibexa/storefront/src/bundle/Resources/views/themes/storefront` directory of your installation. > **Note: Customization and permissions** > > For more information about modifying the storefront components, whether by changing their appearance or modifying the underlying logic, see [Customize the storefront layout](https://doc.ibexa.co/en/latest/templating/layout/customize_storefront_layout/index.md). > > For more information about overriding the default checkout component, see [Customize checkout](https://doc.ibexa.co/en/latest/commerce/checkout/customize_checkout/index.md). > > For information about roles and permissions that control access to various components of the purchase process, see [Permission use cases](https://doc.ibexa.co/en/latest/permissions/permission_use_cases/#commerce). # Configure Storefront Editions: Commerce The Storefront is accessible under the `/product-catalog`. ## Catalog configuration With the `ibexa/storefront` package, you can configure the product catalog and make it available to your shop users. Before you start configuring the Storefront, make sure you have created, configured, and published [catalogs](https://doc.ibexa.co/projects/userguide/en/5.0/pim/work_with_catalogs/#create-catalogs) in the back office. The configuration is available under the `ibexa.system..storefront.catalog` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files). It accepts the following values: 1. All products available for all users: ``` ibexa: system: site: storefront: catalog: ~ ``` If `null`is provided as the value, the Storefront makes the main product catalog (with all products) visible for all users. 2. To expose a single catalog with an identifier to all users, provide a string value of the catalog identifier: ``` ibexa: system: site: storefront: catalog: custom_catalog ``` 3. Specific catalog for the defined customer group You can expose different catalogs based on a customer group assigned to the current user. To do it, provide the following configuration: ``` ibexa: system: site: storefront: catalog: default: standard customer_group: retailer: retailer_catalog wholesale: wholesaler_catalog ``` The basic configuration of the Storefront can look as follows: ``` ibexa: system: site_group: translation_siteaccesses: - site - site_pl - site_fr - site_de page_layout: '@ibexadesign/storefront/layout.html.twig' product_catalog: currencies: - EUR - PLN - USD regions: - poland - france - germany - norway storefront: name: Ibexa logo: 'bundles/ibexaadminui/img/ibexa-logo.svg' catalog: default: main customer_group: vip: vip product_list_limit: 9 product_list_filters: - product_type - product_availability - product_price product_render_action: 'Ibexa\Bundle\Storefront\Controller\ProductRenderController::renderAction' user_settings_groups: - location ``` ## Retrieve catalog assigned to user The `\Ibexa\Contracts\Storefront\Repository\CatalogResolverInterface` interface allows retrieving the product catalog available for a specific user. ``` namespace Ibexa\Contracts\Storefront\Repository; use Ibexa\Contracts\Core\Repository\Values\User\User; use Ibexa\Contracts\ProductCatalog\Values\CatalogInterface; interface CatalogResolverInterface { public function resolveCatalog(?User $user = null): ?CatalogInterface; } ``` `null` stands for the current user. ### Configure user account The following user settings mechanisms used in `ibexa/storefront` are reused from `ibexa/user` package: - [change password feature](https://doc.ibexa.co/en/latest/users/passwords/index.md) - user avatar Settings for a Storefront user are configured under the `ibexa.system..storefront.user_settings_groups` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa: system: site_group: user_settings_groups: - location - custom_group ``` By default, only the `location` user settings is provided: - Currency (from `ibexa/storefront`) - Time zone - Short date and time format - Long date and time format - Language # Extend Storefront Editions: Commerce ## Built-in menus With the `ibexa/storefront` package come the following built-in menus: | Item | Value | Description | | -------------------------------- | --------------- | ---------------------------------------------------------------------------------------------------------- | | [Breadcrumbs](#breadcrumbs-menu) | - | Renders breadcrumbs for content tree root, Taxonomy Entry, product, user settings, and user settings group | | [Taxonomy](#taxonomy-menu) | - | It can render a menu for product categories or tags | | Currency | `currency_menu` | Renders a menu to change the active currency | | Language | `language_menu` | Renders a menu to change the active language | | Region | `region_menu` | Renders a menu to change the active region | Usage example: ``` {% set currency_menu = knp_menu_get('ibexa_storefront.menu.currency') %} {{ knp_menu_render(currency_menu) }} ``` ### Breadcrumbs menu To modify the items in the menu, you need to use an event subscriber. This subscriber replaces the URI under the `Home` link. Create an event subscriber in `src/EventSubscriber/BreadcrumbsMenuSubscriber.php`: ``` ['onBreadcrumbsMenuConfigure', 0], ]; } public function onBreadcrumbsMenuConfigure(ConfigureMenuEvent $event): void { $menu = $event->getMenu(); // Replace link to home with link to product catalog root $menu->getChild('location_2')->setUri('/product-catalog'); } } ``` Next, create the `templates/themes/storefront/storefront/knp_menu/breadcrumbs.html.twig` template: ``` {% extends 'knp_menu.html.twig' %} {% block item %} {% if item.displayed %} {%- set attributes = item.attributes %} {% import _self as knp_menu %} {%- if item.uri is not empty and (not matcher.isCurrent(item) or options.currentAsLink) %} {{ block('linkElement') }} {%- else %} {{ block('spanElement') }} {%- endif %} {% endif %} {% endblock %} {% block linkElement %} {%- set classes = ['breadcrumb_link'] -%} {{ block('label') }} {% endblock %} ``` Next, extend the `templates/themes/storefront/storefront/product.html.twig` template to include the breadcrumbs: ``` {% extends no_layout == true ? view_base_layout : page_layout %} {% trans_default_domain 'storefront' %} {% block content %} {% set breadcrumbs_menu = knp_menu_get('ibexa_storefront.menu.breadcrumbs.product', [], { 'product': product }) %} {{ knp_menu_render(breadcrumbs_menu, { template: '@ibexadesign/storefront/knp_menu/breadcrumbs.html.twig', }) }} {% include '@ibexadesign/storefront/component/product_view.html.twig' %} {% endblock %} {% block javascripts %} {{ parent() }} {% endblock %} ``` ### Taxonomy menu You can build a taxonomy menu for, for example, product categories or tags. See the usage example: ``` {% set categories_menu = knp_menu_get( 'ibexa_storefront.menu.taxonomy', [], { parent: category, depth: 3 } ) %} {{ knp_menu_render(categories_menu) }} ``` It takes the following parameters: | Name | Type | Default | | --------------- | ----------------------------------------------- | ----------------------------------------- | | `parent` | `\Ibexa\Contracts\Taxonomy\Value\TaxonomyEntry` | The root entry of the specified taxonomy. | | `depth` | `int` | Default: 1 | | `taxonomy_name` | `string` | product_categories | ## Create menu items `\Ibexa\Contracts\Storefront\Menu\ItemFactoryInterface` provides convenient methods to build menu item based on repository objects, including: - Content - Content ID - Location - Location ID - Taxonomy Entry - Product ## Generate custom product preview path By default, the `ProductRenderController` controller passes only the product object for rendering. You can modify the controller file to make it pass parameters to the [`path`](https://symfony.com/doc/7.4/reference/twig_reference.html#path) Twig helper function, which is used by the `product_card.html.twig` and `product_card.html.twig` [templates](https://doc.ibexa.co/en/latest/templating/layout/customize_storefront_layout/index.md) to generate the user path. After you modify the controller, it can also pass the following parameters: - `route` - the route, under which product preview is available. - `parameters` - parameters to be used, for example, to render the view. - `is_relative` - Boolean that decides whether the URL is relative or absolute. Define your own logic in a custom controller. Refer to the code snippet below and create your own file, for example, `CustomProductRenderController.php`: ``` public function renderAction(ProductInterface $product): Response { return $this->render('@ibexadesign/storefront/product_card.html.twig', [ 'content' => $product, 'route' => 'some.path', 'parameters' => ['some.parameter' => 123], 'is_relative' => true, ]); } ``` # Transactional emails Editions: Commerce Transactional emails are messages that Ibexa DXP can send through [Actito](https://www.actito.com/en-BE/) gateway to your end-users to notify them about changes in the status of various actions taken in relation to your commerce presence. By default, notifications are sent in relation to the following events, to an email address of the end-user who has originated these events: - Order processing: - order is created - order is processing - order is completed - order is cancelled - Payment: - payment failed - payment has been cancelled - Shipment: - order is shipped - User registration: - user has been registered - Password reset: - password reset request has been submitted You can [change the events](https://doc.ibexa.co/en/latest/commerce/transactional_emails/extend_transactional_emails/#configure-workflows) that trigger sending a transactional email. ## Configure transactional emails ### Install package Transactional email support comes as an additional package that needs to be downloaded separately: ``` composer require ibexa/connector-actito ``` Symfony Flex installs and activates the package. ### Configure Actito integration Before you can start configuring the notifier engine to process and dispatch notifications to be forwarded as transactional emails, you must first obtain and configure an [Actito license](https://www.actito.com/en-BE/pricing/nk). Once you gain access to the Actito dashboard: 1. Configure the API to make calls with the GET method. 1. Get the [API key](https://cdn3.actito.com/fe/actito-documentation/docs/Managing_API_users/) and entity name. 1. Set these values in the YAML configuration files, under the `ibexa.system.default.connector_actito` key: ``` ibexa: system: default: connector_actito: api_key: 12ea56789o1ea56789012ea56789o12e entity: ``` 4. Define profile table in Actito database for storing notification attributes. > **Note: Note** > > By default, a trigger message coming from Ibexa DXP contains the following attributes with information about the end-user: name, surname, and email. > > Those attributes can then be used to present statistics in the Actito dashboard. If this set of attributes is insufficient for your needs, you can [add more attributes to the trigger message](https://doc.ibexa.co/en/latest/commerce/transactional_emails/extend_transactional_emails/#customize-actito-end-user-profile). ### Create email campaigns Create campaigns of transactional email type, one for each notification type that you want to deliver. When you build a campaign template, make sure that you use the variables supported by Ibexa DXP. For a complete list of parameters, see [Transactional email variables reference](https://doc.ibexa.co/en/latest/commerce/transactional_emails/transactional_emails_parameters/index.md). > **Tip: Tip** > > When you invent names for your campaigns, keep them simple, and don't use special characters or spaces. Campaign emails can be sent in one language only. To send emails in different languages, for example, because your application serves end-users from different locales, for each notification and language pair, you must create a separate campaign and [extend the solution to support that](https://doc.ibexa.co/en/latest/commerce/transactional_emails/extend_transactional_emails/#send-emails-in-language-of-commerce-presence). ### Configure mapping After you create and configure campaigns in Actito user interface, one for each type of notifications coming from Ibexa DXP, in YAML configuration files, under the `ibexa.system.default.connector_actito.campaign_mapping` key, you define mappings between notifications and email campaigns, for example: ``` campaign_mapping: Ibexa\Contracts\Payment\Notification\PaymentWorkflowStateChange: campaign: Ibexa\Contracts\OrderManagement\Notification\OrderWorkflowStateChange: campaign: Ibexa\Contracts\Shipping\Notification\ShipmentWorkflowStateChange: campaign: Ibexa\Contracts\User\Notification\UserPasswordReset: campaign: Ibexa\Contracts\User\Notification\UserRegister: campaign: ``` # Transactional email variables reference Editions: Commerce The following variables are provided with an installation of Ibexa. You can use them when you create a template within an Actito transactional email campaign. If this extensive list of variables isn't sufficient, you can [extend it to include additional variables](https://doc.ibexa.co/en/latest/commerce/transactional_emails/extend_transactional_emails/#define-additional-variables). | Category | Variable | Description | Example values | Notes | | ------------------- | --------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------- | --------------------------------------------------- | | Order processing | orderId | Order numerical ID | 123 | | | | orderIdentifier | Order identifier | 660575f7-aa75-47af-b4d3-db2693f7e37c | | | | orderCurrency | Currency code | EUR | | | | orderSource | Order source | storefront | | | | orderValueNet | Total value (net) | €700,00 | | | | orderValueGross | Total value (gross) | €749,50 | | | | orderValueVat | Vat value | €49,50 | | | | shippingAddressCountry | Country code | US | | | | shippingAddressRegion | Region | California | | | | shippingAddressLocality | City | Los Angeles | | | | shippingAddressStreet | Street | 10250 Santa Monica Blvd | | | | shippingAddressPostalCode | Postal code | 90067 | | | | shippingAddressFirstName | Addressee's first name | John | | | | shippingAddressLastName | Addressee's last name | Doe | | | | shippingAddressEmail | E-mail address | [user@example.com](mailto:user@example.com) | | | | shippingAddressPhoneNumber | Phone number | 123456789 | | | | billingAddressCountry | Country code | US | | | | billingAddressTaxId | Tax Identification Number i.e. VAT | 12345678 | | | | billingAddressRegion | Region | California | | | | billingAddressLocality | City | Los Angeles | | | | billingAddressStreet | Street | 10250 Santa Monica Blvd | | | | billingAddressPostalCode | Postal code | 90067 | | | | billingAddressFirstName | Payer's first name | John | | | | billingAddressLastName | Payer's last name | Doe | | | | billingAddressEmail | E-mail address | [user@example.com](mailto:user@example.com) | | | | billingAddressPhoneNumber | Phone number | 123456789 | | | Payment | paymentMethodIdentifier | Technical identifier of payment method | | | | | paymentMethodName | Human readable name of payment method | | | | | paymentMethodDescription | Human readable description of payment method | Prepaid cards and gift cards (offline version) | | | | paymentMethodTypeName | Human readable name of payment method type | Offline | | | | paymentStatus | Technical identifier of payment status | pending, failed | Only available in PaymentStatusChange notification | | Shipment | shippingMethodIdentifier | Technical identifier of shipping method | | | | | shippingMethodName | Human readable name of shipping method | | | | | shippingMethodDescription | Human readable description of shipping method | | | | | shippingMethodTypeName | Technical name of shipping method type | | | | | shipmentStatus | Technical identifier of shipment status | | Only available in ShipmentStatusChange notification | | Product information | products.id | Product numerical ID | 123 | | | | products.code | Product code (SKU) | 123456 | | | | products.name | Product name | iPhone 15 Pro 256GB Space Gray | | | | products.url | Product view URL | | | | | products.thumbnail | Product thumbnail URL | | | | | products.quantity | Quantity | 5 | | | | products.unitPriceNet | Unit price (net) | €700,00 | | | | products.unitPriceGross | Unit Price (gross) | €749,50 | | | | products.subtotalPriceNet | Subtotal price (net), quantity * unit price (net) | €2700,00 | | | | products.subtotalPriceGross | Subtotal price (gross), quantity * unit price (gross) | €2749,50 | | | User information | userId | Numerical ID | 255 | | | | userLogin | User login | john.doe | | | | userEmail | User e-mail address | [john.doe@example.com](mailto:john.doe@example.com) | | | | userName | User name | John Doe | | | Password reset | token | Token used to reset password | 5bcc871f1a966db58c06187369813447 | | | | passwordResetUrl | Absolute URL to reset password | | | # Customize transactional emails Editions: Commerce Customizing the transactional email feature allows for better alignment with your specific business requirements. ## Configure workflows Ibexa DXP uses workflows to define processes in various Commerce components. Predefined workflows exist for [order processing](https://doc.ibexa.co/en/latest/commerce/order_management/configure_order_management/#default-order-processing-configuration), [payment](https://doc.ibexa.co/en/latest/commerce/payment/configure_payment/#default-payment-workflow-configuration) and [shipment](https://doc.ibexa.co/en/latest/commerce/shipping_management/configure_shipment/#default-shipment-workflow-configuration). You can customize those workflows to trigger pushing notifications at various places of these workflows, for example: ``` framework: workflows: ibexa_payment: # ... places: pending: metadata: # ... trigger_notification: true # true or false ``` ## Define additional variables Ibexa DXP comes with a predefined [set of variables](https://doc.ibexa.co/en/latest/commerce/transactional_emails/transactional_emails_parameters/index.md) that you can use when building a template for your transactional email campaign at Actito. If this list isn't sufficient, you can use Events to include additional variables: ``` 'onParametersFactoryEvent', ]; } public function onParametersFactoryEvent(ParametersFactoryEvent $event): void { $event->addParameter( new SimpleParameter( 'newVariable', ['value'], ), ); $event->addParameter( new SimpleParameter( 'anotherVariable', ['multiple', 'values'], ), ); } } ``` ## Customize Actito end-user profile The Actito platform offers many features for customer data collection, including segmentation, subscriptions, and interaction tracking. This information can be later user for generating statistics, establishing trends, or used to calculate Personalization recommendations. To use these features you need to provide profile data to API requests yourself. You do it by means of events that are triggered during profile building. For example, the `Ibexa\Contracts\ConnectorActito\Event\TransactionalMailRequest\ProfileFactoryEvent` event is triggered for every transactional notification, and it lets you set required data that is passed to Actito API: ``` 'onProfileFactoryEvent', ]; } public function onProfileFactoryEvent(ProfileFactoryEvent $event): void { $recipient = $event->getRecipient(); $profile = $event->getProfile(); $user = $recipient->getUser(); // Provide additional data if your profile has more attributes: $attributes = $profile->getAttributes(); $attributes[] = new Attribute('name', $user->getName()); $profile->setAttributes($attributes); // Passing segmentation data to the profile $segmentations = [ new Segmentation( 'Frequent visitors', 'storefront_users', true, ), ]; $profile->setSegmentations($segmentations); // Use the same mechanism to pass other profile data $profile->setSubscriptions(...); $profile->setDataCollection(...); } } ``` ## Send emails in language of commerce presence Actito supports sending out emails in one language only per campaign. To send emails in different languages from one notification, for example, because your application serves end-users from different locales, for each notification and language pair, you must create a separate campaign. You could do it by adding a language suffix to a campaign name. On Ibexa DXP side, to support this scenario, you must use an Event Subscriber on `Ibexa\Contracts\ConnectorActito\Event\ResolveCampaignEvent`: ``` 'onCampaignResolve', ]; } public function onCampaignResolve(ResolveCampaignEvent $event): void { // you can use below data in your logic $resolvedCampaign = $event->getCampaign(); $recipient = $event->getRecipient(); $notification = $event->getNotification(); // when new campaign was determined, set it to the event $campaign = new Campaign('new_order_created_12-2023_en-US'); $event->setCampaign($campaign); } } ``` # Discounts # Discounts Editions: Commerce With the Discounts feature, store managers can reduce prices on specific products or categories for all or selected customers. After you install it, temporary or permanent discounts can be applied against items from the product catalog or cart. You can also extend the feature, for example, by creating custom pricing rules, application conditions, or changing discount priorities. ## Getting Started - [Discounts product guide](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/discounts/discounts_guide/): Discount enable reducing prices on products or product categories based on a detailed logic resolution. - [Customize Discounts](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/discounts/configure_discounts/): Customize the behavior of the Discounts feature. - [Policies](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/permissions/policies/#discounts): Learn about the available Discounts policies - [Work with Discounts](https://doc.ibexa.co/projects/userguide/en/5.0/commerce/discounts/work_with_discounts/): Create and edit discounts, toggle discount status. ## Development - [Discounts API](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/discounts/discounts_api/): Discounts enable reducing prices on products or product categories based on a detailed logic resolution. - [Extend Discounts](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/discounts/extend_discounts/): Extend Discounts by adding your own rules and conditions - [Extend Discounts wizard](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/discounts/extend_discounts_wizard/): Integrate custom rules and conditions into the back office forms. - [Discounts events](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/api/event_reference/discounts_events/): Events that are triggered when working with discounts. - [REST API Reference](https://doc.ibexa.co/en/5.0/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Discounts): See the available endpoints for Discounts - [Discounts Twig functions](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/templating/twig_function_reference/discounts_twig_functions/): Discounts Twig Functions allow you to operate on discounts in your templates. - [Discounts Search Criterion reference](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/search/discounts_search_reference/discounts_criteria/): Search Criteria available for Discounts search - [Discounts Search Sort Clauses reference](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/search/discounts_search_reference/discounts_sort_clauses/): Sort Clauses available for Discounts search - [Importing Discounts](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/content_management/data_migration/importing_data/#discounts): Learn how to manage Discounts using data migrations # Discounts product guide Editions: Commerce ## What are discounts Just like brick-and-mortar shops, online stores use clever strategies to attract new customers, keep loyal ones, boost sales, highlight special products, and clear out inventory. One powerful technique that helps achieve these goals is offering discounts. Discounts allow online stores to temporarily or permanently reduce prices on specific products or categories, making deals more attractive to potential buyers. They can be used to encourage first-time purchases, reward loyal customers, promote new or slow-moving items, or drive sales during seasonal events. By displaying discounted prices clearly in the catalog or cart, businesses can create a sense of urgency, increase customer satisfaction, and ultimately boost revenue. Ibexa DXP comes equipped with the Discounts feature that introduces a highly extensible solution for building price reductions. Store managers can create general discounts that apply for products from the product catalog or specific discounts that apply for products in the customer's shopping cart. They can choose how the discount is calculated and set conditions to decide when their discounts are applied. The conditions used to limit the applicability of a discount include, for example, rules that check whether: - the product belongs to a specific category - the customer belongs to a specific customer group - minimum purchase amount (total cart value) is met - minimum purchase quantity (per product) is met > **Note: Difference between discounts and price rules** > > Unlike flexible and highly configurable discounts, [prices applied to customer groups](https://doc.ibexa.co/en/latest/product_catalog/prices/#custom-pricing) cannot have time limits, only apply to specific customer groups, and do not offer flexibility to adjust prices at cart level. ## Availability Discounts are available as part of the [Ibexa Commerce](https://doc.ibexa.co/en/latest/ibexa_products/ibexa_commerce/index.md) edition. ## How it works The Discounts feature hooks into the price resolving logic of products, allowing you to modify it before it's displayed to the customers. ### Core concepts #### Discounts Discounts are reductions in the price of a product, typically implemented as part of a marketing campaign. Discounts are applied in two places: - **Product catalog** - catalog discounts are activated when browsing the product catalog and do not require any action from the customer to be activated - **Cart** - cart discounts can activate when entering the [cart](https://doc.ibexa.co/en/latest/commerce/cart/cart/index.md), if the right conditions are met. They may also require entering a discount code to be activated A shopping cart can have multiple active discounts, but a specific product can only have a single discount applied to it at a time. #### Discounts priority When two or more discounts can be applied to a single product, the system evaluates the following properties to choose the right one: - discount code existence (discounts with discount codes have priority over the others) - discount activation place (cart discounts rank higher over catalog discounts) - discount priority (higher priority ranks higher) - discount creation date (newer discounts rank higher) The properties are evaluated in the order given above until a single discount is selected. #### Discount properties After choosing where the discount applies (catalog or cart), you can choose the discount type: - **Fixed amount** - where a specified amount of money, for example, 5 Euro, is deducted from the base price of the product - **Percentage** - where a specified percentage, for example, 10%, is used to calculate the deducted amount from the product's base price Discounts are translatable and you can limit them to specific [regions](https://doc.ibexa.co/en/latest/product_catalog/product_catalog_guide/#regions) or a single currency. They can be permanent or be active only in a specified time frame. Regardless of the specified dates, you can disable a discount at any time to prevent customers from using it. The discount data is split into two parts: - name and description add internal information for the store managers - promotion information add additional information displayed to the customers #### Target groups With discounts, you can target your entire customer base or only a subset of it belonging to specified [customer groups](https://doc.ibexa.co/en/latest/users/customer_groups/index.md). #### Product selection All products, including [product variants](https://doc.ibexa.co/en/latest/product_catalog/product_catalog_guide/#product-variants), can be selected when creating a discount. You can also limit this choice to a subset of products: - belonging to selected [product categories](https://doc.ibexa.co/en/latest/product_catalog/product_catalog_guide/#product-categories) - hand-picked manually for special cases #### Conditions Use conditions to limit the applicability of a discount, for example by checking that: - the product belongs to a specific category - the customer belongs to a specific customer group before applying the discount. For **cart discounts**, you can specify additional conditions that must be met for the discount to apply. These conditions can include: - minimum purchase quantity (per product) - minimum purchase amount (total cart value) - special [discount codes](#discount-codes) See [the built-in list of conditions](https://doc.ibexa.co/en/latest/discounts/discounts_api/#conditions) and [creating custom conditions](https://doc.ibexa.co/en/latest/discounts/extend_discounts/#implement-custom-condition) for more information. ##### Discount codes For **cart discounts**, you can specify an additional text value that needs to be entered in the cart for the discount to apply. The discount code usage can be limited globally, for example by making the discount valid only for the first 10 customers before it expires. You can also limit the usage per customer: - single use: every customer can use this code only once - limited use: every customer can use the code a specified number of times - unlimited ### Discount re-indexing Discounts affect the prices shown in the product catalog. When a discount is created, updated, or expires, the product catalog must be re-indexed to ensure that the search results and product listings display correct prices. To prevent performance disruptions which could occur if re-indexing occurred immediately, Ibexa DXP uses the Ibexa Messenger's [background queue](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/background_tasks/index.md) to process re-indexing tasks in the background. By [configuring the process](https://doc.ibexa.co/en/latest/discounts/configure_discounts/#discount-re-indexing), you ensure that re-indexing is performed at the most convenient time to maintain your application's overall stability. ## Capabilities ### Management Users with the appropriate permissions, governed by role-based policies, can control the lifecycle of discounts by creating, editing, and deleting them. Additionally, discount configurations can be enabled or disabled depending on the organization's needs. *[Image: Discount management screen]* An intuitive discounts interface displays a list of all available discounts. Here, you can search for specific discounts and filter them by type, status, or more. By accessing the detailed view of individual discounts, you can quickly review all their parameters. ### Extensibility Built-in discount types offer a good starting point, but the real power of the discounts lies in extensibility. Extending discounts opens up new possibilities for building promotional campaigns that help move stock and attach customers. For example, Ibexa DXP could apply a special discount when a customer places their 1st, 3rd, or 100th order in the storefront. This encourages first-time purchases and drives long-term customer loyalty. ## Use cases Out of the box, the Ibexa Discounts feature comes with multiple discount types that can be applied in the following use cases. ### End of Season Sale Create a permanent discount for products manufactured last season to increase attention for them. ### Temporary sales Create urgency by offering promoted sales that are active only in a specified time frame to attract new customers or increase conversation, for example during events like Black Week or Cyber Monday. ### Reward loyal customers Make your newsletters readers or chosen customer groups feel special by providing them with a dedicated discount that applies only to them, either by manually selecting a target audience, or by using a discount code. ### Reward large purchases Encourage larger purchases and increase the average order size by applying an automatic discount when the purchase amount or quantity exceeds specified threshold. # Customize Discounts Editions: Commerce You can customize the behavior of the Discounts feature by using the following [configuration](https://doc.ibexa.co/en/latest/administration/configuration/configuration/index.md): ## Back Office pagination Use the built-in SiteAccess-aware parameters to change the default pagination settings. The following parameters are available: - `list_per_page_limit` controls the number of discounts displayed on a single page in discount list view - `products_list_per_page_limit` controls the number of products displayed on a single page in a discount details view You can set them as in the following example: ``` ibexa: system: admin_group: discounts: pagination: list_per_page_limit: 10 products_list_per_page_limit: 15 ``` ## Discount re-indexing Discounts feature uses Ibexa Messenger to reindex discounts and product prices as [background tasks](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/background_tasks/index.md). This way changes are processed efficiently without slowing down the system and disrupting the user experience. When triggered periodically, the `ibexa:discounts:reindex` command identifies discounts that require re-indexing, ensuring prices always remain up-to-date. If there are edits to discounts that should result in changed product catalog prices, messages are dispatched to the Ibexa Messenger's queue and consumed by a background worker. The worker passes the messages to the handler, which then starts the re-indexing process at the most convenient moment. To run discount re-indexing in the background: 1. Make sure that the transport layer is [defined properly](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/background_tasks/#configure-package) in Ibexa Messenger configuration. 1. Make sure that the [worker starts](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/background_tasks/#start-worker) together with the application to watch the transport bus: ``` php bin/console messenger:consume ibexa.messenger.transport --bus=ibexa.messenger.bus ``` 3. Use a scheduler of your choice, for example, [cron](https://en.wikipedia.org/wiki/Cron), to periodically run the following command: ``` php bin/console ibexa:discounts:reindex ``` > **Note: Deploying Symfony Messenger** > > For more information about deploying the Messenger to production, see [Symfony documentation](https://symfony.com/doc/7.4/messenger.html#deploying-to-production). ## Rate limiting To prevent malicious actors from trying all the possible discount code combinations using brute-force attacks, the [`/discounts_codes/{cartIdentifier}/apply` endpoint](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#discount-codes-apply-discount-to-cart) is rate limited using the [Rate Limiter Symfony component](https://symfony.com/doc/7.4/rate_limiter.html). You can adjust the default configuration by modifying the `config/packages/ibexa_discounts_codes.yaml` file created during installation process. The limiter uses the following pattern: `user_%d_ip_%s`, using Customer ID and Customer IP address to track usage of both logged-in and anonymous customers. To cover additional use cases, you can add your own logic by listening to the [`BeforeDiscountCodeApplyEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-DiscountsCodes-Event-BeforeDiscountCodeApplyEvent.html) event. ## Checkout error-handling A discount can be valid when customer enters the cart, but later become invalid before the checkout process is completed. For example, this event could occur if the discount expired, was modified, disabled, or deleted before the customer completed the checkout process. To prevent customers from placing such orders, the Discounts feature comes with built-in error-handling. Once it detects that a discount that can no longer be used is applied to a product, it stops the checkout process and informs the customer. This error handling is provided by two event subscribers: - `Ibexa\Bundle\Checkout\EventSubscriber\DiscountsHaveChangedExceptionSubscriber` - `Ibexa\Bundle\DiscountsCodes\EventSubscriber\DiscountCodeUnusableExceptionSubscriber` You can disable this behavior by setting the `ibexa_checkout.error_handlers.enabled` container parameter to `false`, which allows you to provide your own solution for these cases. # Discounts API Editions: Commerce ## Manage discounts and discount codes By integrating with the [Discount feature](https://doc.ibexa.co/en/latest/discounts/discounts_guide/index.md) you can automate the process of managing discounts, streamlining the whole process and automating business rules. For example, you can automatically create a discount when a customer places their 3rd order, encouraging them to make another purchase and increase their chances of becoming a loyal customer. You can manage discounts using [data migrations](https://doc.ibexa.co/en/latest/content_management/data_migration/importing_data/#discounts), [REST API](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#discounts), or the PHP API by using the [`Ibexa\Contracts\Discounts\DiscountServiceInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountServiceInterface.html) service. The core concepts when working with discounts through the APIs are listed below. ### Types When using the PHP API, the discount type defines where the discount can be applied. Discounts are applied in two places, listed in the [`DiscountType`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-DiscountType.html) class: - **Product catalog** - `catalog` discounts are activated when browsing the product catalog and do not require any action from the customer to be activated - **Cart** - `cart` discounts can activate when entering the [cart](https://doc.ibexa.co/en/latest/commerce/cart/cart/index.md), if the right conditions are met. They may also require entering a discount code to be activated Regardless of activation place, discounts always apply to products and reduce their base price. To define when a discount activates and how the price is reduced, use rules and conditions. They use the [Symfony Expression language](https://symfony.com/doc/7.4/components/expression_language.html) to express their logic. ### Rules Discount rules define how to calculate the price reduction. The following discount rule types are available in the `\Ibexa\Discounts\Value\DiscountRule` namespace: | Rule type (identifier) | Description | Required expression value | | ------------------------------ | ----------------------------------------------------------------------- | ------------------------- | | `FixedAmount` (`fixed_amount`) | Deducts the specified amount, for example 10 EUR, from the base price | `discount_amount` | | `Percentage` (`percentage`) | Deducts the specified percentage, for example -10%, from the base price | `discount_percentage` | Only a single discount can be applied to a given product, and a discount can only have a single rule. When creating a rule, not with the user interface but an API, you must pass the required expression values for the rule to be valid: - using PHP, the values are passed through the constructor which converts them into an expression variable - using data migrations and the REST API, the values are specified using the `expressionValues` key See the following examples for data migrations and the REST API usage: - creating discounts with [data migrations](https://doc.ibexa.co/en/latest/content_management/data_migration/importing_data/#discounts): ``` - type: discount mode: create # ... rule: type: percentage expressionValues: discount_percentage: 10 ``` - parsing responses from the [REST API](https://doc.ibexa.co/en/4.6/api/rest_api/rest_api_reference/rest_api_reference.html#discounts): ``` { "Discount": { "_media-type": "application/vnd.ibexa.api.Discount+json", "id": 1, "identifier": "summersale2025", "name": "SummerSale2025", "type": "catalog", "label": "Summer Sale 2025", "labelDescription": "Summer Sale 2025", "priority": 1, "isEnabled": true, "createdAt": "2025-06-10T09:43:42+00:00", "updatedAt": "2025-06-10T09:48:23+00:00", "startDate": "2025-07-01T09:34:19+00:00", "endDate": "2025-07-31T12:00:00+00:00", "DiscountExpressionAware": { "_media-type": "application/vnd.ibexa.api.DiscountExpressionAware+json", "expressionValues": { "discount_amount": "10" } }, } ``` ### Conditions With conditions you can narrow down the scenarios in which the discount applies. The following conditions are available in the `\Ibexa\Discounts\Value\DiscountCondition` and `\Ibexa\DiscountsCodes\Value\DiscountCondition` namespaces: | Condition (identifier) | Applies to | Description | Required expression values | | -------------------------------------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | | `IsInCategory` (`is_in_category`) | Cart, Catalog | Checks if the product belongs to specified [product categories](https://doc.ibexa.co/projects/userguide/en/5.0/pim/work_with_product_categories/) | `categories` | | `IsInCurrency` (`is_in_currency`) | Cart, Catalog | Checks if the product has price in the specified currency | `currency_code` | | `IsInRegions` (`is_in_regions`) | Cart, Catalog | Checks if the customer is making the purchase in one of the specified regions | `regions` | | `IsProductInArray` (`is_product_in_array`) | Cart, Catalog | Checks if the product belongs to the group of selected products | `product_codes` | | `IsUserInCustomerGroup` (`is_user_in_customer_group`) | Cart, Catalog | Check if the customer belongs to specified [customer groups](https://doc.ibexa.co/en/latest/users/customer_groups/index.md) | `customer_groups` | | `IsProductInQuantityInCart` (`is_product_in_quantity_in_cart`) | Cart | Checks if the required minimum quantity of a given product is present in the cart | `quantity` | | `MinimumPurchaseAmount` (`minimum_purchase_amount`) | Cart | Checks if purchase amount in the cart exceeds the specified minimum | `minimum_purchase_amount` | | `IsValidDiscountCode` (`is_valid_discount_code`) | Cart | Checks if the correct discount code has been provided and how many times it was used by the customer | `discount_code`, `usage_count` | When multiple conditions are specified, all of them must be met. As with rules, when creating a condition through other means than the user interface, you must pass the required expression values for the condition to be valid: - using PHP, the values are passed through the constructor which converts them into an expression variable - using data migrations and the REST API, the values are specified using the `expressionValues` key See the following examples for data migrations and the REST API usage: - creating discounts with [data migrations](https://doc.ibexa.co/en/latest/content_management/data_migration/importing_data/#discounts): ``` - type: discount mode: create # ... conditions: - identifier: is_in_currency expressionValues: currency_code: EUR - identifier: is_product_in_array expressionValues: product_codes: - product_code_book_0 - product_code_book_1 ``` - parsing responses from the [REST API](https://doc.ibexa.co/en/4.6/api/rest_api/rest_api_reference/rest_api_reference.html#discounts): ``` { "Discount": { "_media-type": "application/vnd.ibexa.api.Discount+json", "id": 1, "identifier": "summersale2025", "name": "SummerSale2025", "type": "catalog", "label": "Summer Sale 2025", "labelDescription": "Summer Sale 2025", "priority": 1, "isEnabled": true, "createdAt": "2025-06-10T09:43:42+00:00", "updatedAt": "2025-06-10T09:48:23+00:00", "startDate": "2025-07-01T09:34:19+00:00", "endDate": "2025-07-31T12:00:00+00:00", "DiscountConditions": [ { "_media-type": "application/vnd.ibexa.api.DiscountExpressionAware+json", "expressionValues": { "currency_code": "USD" } } ], } ``` ### Priority You can set discount priority as a number between 1 and 10 to indicate which discount should have [higher priority](https://doc.ibexa.co/en/latest/discounts/discounts_guide/#discounts-priority) when choosing the one to apply. ### Start and end date Discounts can be permanent, or valid only in a specified time frame. Every discount has a start date, which defaults to the date when the discount was created. The end date can be set to `null` to make the discount permanent. ### Status You can disable a discount anytime to stop it from being active, even if the conditions enforced by start and end date are met. Only disabled discounts can be deleted. ### Discount translations The discount has four properties that can be translated: | Property | Usage | | --------------------- | --------------------------------------- | | Name | Internal information for store managers | | Description | Internal information for store managers | | Promotion label | Information displayed to customers | | Promotion description | Information displayed to customers | Use the [`DiscountTranslationStruct`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Struct-DiscountTranslationStruct.html) to provide translations for discounts. ### Discount codes To activate a cart discount only after a proper discount code is provided, you need to: 1. Create a discount code using the [`DiscountCodeServiceInterface::createDiscountCode()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-DiscountsCodes-DiscountCodeServiceInterface.html#method_createDiscountCode) method 2. Attach it to a discount by using the `IsValidDiscountCode` condition Set the [`usedLimit`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-DiscountsCodes-Value-Struct-DiscountCodeCreateStruct.html#method___construct) property to the number of times a single customer can use this code, or to `null` to make the usage unlimited. The [`DiscountCodeServiceInterface::registerUsage()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-DiscountsCodes-DiscountCodeServiceInterface.html#method_registerUsage) method is used to track the number of times a discount code has been used. ### Example API usage The example below contains a Command creating a cart discount. The discount: - has the highest possible [priority](#priority) value - [rule](#rules) deducts 10 EUR from the base price of the product - is [permanent](#start-and-end-date) - [depends](#conditions) on - being bought from Germany or France - 2 products - a `summer10` [discount code](#discount-codes) which can be used only 10 times, but a single customer can use the code multiple times ``` permissionResolver->setCurrentUserReference( $this->userService->loadUserByLogin('admin') ); $now = new DateTimeImmutable(); $discountCodeCreateStruct = new DiscountCodeCreateStruct( 'summer10', 10, // Global usage limit null, // Unlimited usage per customer $this->permissionResolver->getCurrentUserReference()->getUserId(), $now ); $discountCode = $this->discountCodeService->createDiscountCode($discountCodeCreateStruct); $discountCreateStruct = new DiscountCreateStruct(); $discountCreateStruct ->setIdentifier('discount_identifier') ->setType(DiscountType::CART) ->setPriority(10) ->setEnabled(true) ->setUser($this->userService->loadUserByLogin('admin')) ->setRule(new FixedAmount(10)) ->setStartDate($now) ->setConditions([ new IsInRegions(['germany', 'france']), new IsProductInArray(['product-1', 'product-2']), new IsInCurrency('EUR'), new IsValidDiscountCode( $discountCode->getCode(), $discountCode->getGlobalLimit(), $discountCode->getUsedLimit() ), ]) ->setTranslations([ new DiscountTranslationStruct('eng-GB', 'Discount name', 'This is a discount description', 'Promotion Label', 'Promotion Description'), new DiscountTranslationStruct('ger-DE', 'Discount name (German)', 'Description (German)', 'Promotion Label (German)', 'Promotion Description (German)'), ]) ->setEndDate(null) // Permanent discount ->setCreatedAt($now) ->setUpdatedAt($now) ->setContext(new ArrayMap(['custom_context' => 'custom_value'])); $this->discountService->createDiscount($discountCreateStruct); return Command::SUCCESS; } } ``` Similarly, use the `deleteDiscount`, `deleteTranslation`, `disableDiscount`, `enableDiscount`, and `updateDiscount` methods from the [DiscountServiceInterface](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountServiceInterface.html) to manage the discounts. You can always attach additional logic to the Discounts API by listening to the [available events](https://doc.ibexa.co/en/latest/api/event_reference/discounts_events/index.md). ## Search You can search for Discounts using the [`DiscountServiceInterface::findDiscounts()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountServiceInterface.html#method_findDiscounts) method. To learn more about the available search options, see Discounts' [Search Criteria](https://doc.ibexa.co/en/latest/search/discounts_search_reference/discounts_criteria/index.md) and [Sort Clauses](https://doc.ibexa.co/en/latest/search/discounts_search_reference/discounts_sort_clauses/index.md). For discount codes, you can query the database for discount code usage using [`DiscountCodeServiceInterface::findCodeUsages()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-DiscountsCodes-DiscountCodeServiceInterface.html#method_findCodeUsages) and [`DiscountCodeUsageQuery`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-DiscountsCodes-Value-Query-DiscountCodeUsageQuery.html). ## Retrieve applied discounts The applied discounts change final product pricing. To learn more about working with prices, see [Price API](https://doc.ibexa.co/en/latest/product_catalog/price_api/#prices). The example below shows how you can use: - [`ProductPriceServiceInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-ProductPriceServiceInterface.html) to query for base product prices - [`PriceResolverInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-PriceResolverInterface.html) to query for final product prices - [`PriceEnvelopeInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Price-PriceEnvelopeInterface.html) to retrieve applied discounts - [`OrderServiceInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-OrderManagement-OrderServiceInterface.html) to display discount details for [orders](https://doc.ibexa.co/en/latest/commerce/order_management/order_management/index.md) ``` permissionResolver->setCurrentUserReference($this->userService->loadUserByLogin('admin')); $productCode = 'product_code_control_unit_0'; $orderIdentifier = '4315bc58-1e96-4f21-82a0-15f736cbc4bc'; $currencyCode = 'EUR'; $output->writeln('Product data:'); $product = $this->productService->getProduct($productCode); $currency = $this->currencyService->getCurrencyByCode($currencyCode); $basePrice = $this->productPriceService->getPriceByProductAndCurrency($product, $currency); $resolvedPrice = $this->priceResolver->resolvePrice($product, new PriceContext($currency)); if ($resolvedPrice === null) { throw new Exception('Could not resolve price for the product'); } $output->writeln(sprintf('Base price: %s', $this->formatPrice($basePrice->getMoney()))); $output->writeln(sprintf('Discounted price: %s', $this->formatPrice($resolvedPrice->getMoney()))); if ($resolvedPrice instanceof PriceEnvelopeInterface) { /** @var \Ibexa\Discounts\Value\Price\Stamp\DiscountStamp $discountStamp */ foreach ($resolvedPrice->all(DiscountStamp::class) as $discountStamp) { $output->writeln( sprintf( 'Discount applied: %s , new amount: %s', $discountStamp->getDiscount()->getName(), $this->formatPrice( $discountStamp->getNewPrice() ) ) ); } } $output->writeln('Order details:'); $order = $this->orderService->getOrderByIdentifier($orderIdentifier); foreach ($order->getItems() as $item) { /** @var ?DiscountsData $discountData */ $discountData = $item->getContext()['discount_data'] ?? null; if ($discountData instanceof DiscountsData) { $output->writeln( sprintf( 'Product bought with discount: %s, base price: %s, discounted price: %s', $item->getProduct()->getName(), $this->formatPrice($discountData->getOriginalPrice()), $this->formatPrice( $item->getValue()->getUnitPriceGross() ) ) ); } else { $output->writeln( sprintf( 'Product bought with original price: %s, price: %s', $item->getProduct()->getName(), $this->formatPrice( $item->getValue()->getUnitPriceGross() ) ) ); } } return Command::SUCCESS; } private function formatPrice(Money $money): string { return $this->moneyFactory->getMoneyFormatter()->format($money); } } ``` # Extend Discounts Editions: LTS Update, Commerce By extending [Discounts](https://doc.ibexa.co/en/latest/discounts/discounts_guide/index.md), you can increase flexibility and control over how promotions are applied to suit your unique business rules. Together with the existing [events](https://doc.ibexa.co/en/latest/api/event_reference/event_reference/index.md) and the [Discounts PHP API](https://doc.ibexa.co/en/latest/discounts/discounts_api/index.md), extending discounts gives you the ability to cover additional use cases related to selling products. > **Tip: Tip** > > If you prefer learning from videos, two presentations from Ibexa Summit 2025 cover the Discounts feature: > > - [*Introduction to the Discounts system in Ibexa DXP*](https://www.youtube.com/watch?v=kTgtxY38srw) by Konrad Oboza > - [*Extending new Discounts to suit your needs*](https://www.youtube.com/watch?v=pDJxEKJLwPs) by Paweł Niedzielski ## Create custom conditions and rules With custom [conditions](https://doc.ibexa.co/en/latest/discounts/discounts_api/#conditions) and [rules](https://doc.ibexa.co/en/latest/discounts/discounts_api/#rules) you can create more advanced discounts that apply only in specific scenarios. For both of them, you need to specify their logic with [Symfony's expression language](https://symfony.com/doc/current/components/expression_language.html). ### Available expressions You can use the following built-in expressions (variables and functions) in your own custom conditions and rules. You can also [create your own](#custom-expressions). | Type | Name | Value | Available for | | -------- | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | | Function | `get_current_region()` | [Region object](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-RegionInterface.html) of the current siteaccess. | Conditions, rules | | Function | `is_in_category()` | `true/false`, depending if a product belongs to given [product categories](https://doc.ibexa.co/en/latest/product_catalog/product_catalog_guide/#product-categories). | Conditions, rules | | Function | `is_user_in_customer_group()` | `true/false`, depending if an user belongs to given [customer groups](https://doc.ibexa.co/en/latest/users/customer_groups/index.md). | Conditions, rules | | Function | `calculate_purchase_amount()` | Purchase amount, calculated for all products in the cart before the discounts are applied. | Conditions, rules | | Function | `is_product_in_product_codes()` | Parameters: - [Product object](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-ProductInterface.html) - array of product codes Returns `true` if the product is part of the given list. | Conditions, rules | | Function | `is_valid_discount_code()` | Parameter: discount code (string). Returns `true` if the discount code is valid for current user. | Conditions, rules | | Variable | `cart` | [Cart object](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Cart-Value-CartInterface.html) associated with current context. | Conditions, rules | | Variable | `currency` | [Currency object](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-CurrencyInterface.html) of the current siteaccess. | Conditions, rules | | Variable | `customer_group` | [Customer group object](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-CustomerGroupInterface.html) associated with given price context or the current user. | Conditions, rules | | Variable | `product` | [Product object](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-ProductInterface.html) | Conditions, rules | | Variable | `amount` | Original price of the product | Rules | ### Custom expressions You can create your own variables and functions to make creating the conditions easier. The examples below show how to add an additional variable and a function to the available ones: - New variable: `current_user_registration_date` It's a [`DateTime`](https://www.php.net/manual/en/class.datetime.php) object with the registration date of the currently logged-in user. To add it, create a class implementing the [`DiscountVariablesResolverInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountVariablesResolverInterface.html): ``` $this->userService->loadUser( $this->permissionResolver->getCurrentUserReference()->getUserId() )->getContentInfo()->publishedDate, ]; } } ``` And mark it as a service using the `ibexa.discounts.expression_language.variable_resolver` service tag: ``` App\Discounts\ExpressionProvider\CurrentUserRegistrationDateResolver: tags: - ibexa.discounts.expression_language.variable_resolver ``` - New function: `is_anniversary()` It's a function returning a boolean value indicating if today is the anniversary of the date passed as an argument. The function accepts an optional argument, `tolerance`, allowing you to extend the range of dates that are accepted as anniversaries. This implementation is simplified and does not cover the approach for accounts created on February 29 during leap years. ``` unifyYear(new DateTimeImmutable()); $d2 = $this->unifyYear($date); $diff = $d1->diff($d2, true)->days; // Check if the difference between dates is within the tolerance return $diff <= $tolerance; } private function unifyYear(DateTimeInterface $date): DateTimeImmutable { // Create a new date using the reference year but with the same month and day $newDate = DateTimeImmutable::createFromFormat( self::YEAR_MONTH_DAY_FORMAT, self::REFERENCE_YEAR . '-' . $date->format(self::MONTH_DAY_FORMAT) ); if ($newDate === false) { throw new \RuntimeException('Failed to unify year for date.'); } return $newDate; } } ``` Mark it as a service using the `ibexa.discounts.expression_language.function` service tag and specify the function name in the service definition. ``` App\Discounts\ExpressionProvider\IsAnniversaryResolver: tags: - name: ibexa.discounts.expression_language.function function: is_anniversary ``` Two new expressions are now available for use in custom conditions and rules. When deciding whether to register a new custom variable or function, consider the following: - variables are always evaluated by the expression engine and the result is available for all the rules and conditions specified in the discount - functions are invoked only when the rule or condition using them is evaluated. If there are multiple conditions using them, they will be invoked multiple times For performance reasons, it's recommended to: - use variables only for lightweight calculations - use functions for resource-intensive calculations (for example, checking customer's order history) - implement caching (for example, in-memory) for function results to avoid redundant calculations when multiple discounts expressions might use the function - specify the most resource-intensive conditions as the last to evaluate. As all conditions must be met for the discount to apply, it's possible to skip evaluating them if the previous ones won't be met In a production implementation, you should consider refactoring the `current_user_registration_date` variable into a `get_current_user_registration_date` function to avoid always loading the current user object and improve performance. ### Implement custom condition The following example creates a new discount condition. It allows you to offer a special discount for customers on the date when their account was created, making use of the expressions added above. Create the condition by creating a class implementing the [`DiscountConditionInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-DiscountConditionInterface.html): ``` $tolerance ?? 0, ]); } public function getTolerance(): int { return $this->getExpressionValue('tolerance'); } public function getIdentifier(): string { return self::IDENTIFIER; } public function getExpression(): string { return 'is_anniversary(current_user_registration_date, tolerance)'; } } ``` This condition can be used in both catalog and cart discounts. To implement a cart-only discount, additionally implement the marker [`CartDiscountConditionInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-CartDiscountConditionInterface.html) interface. The `tolerance` option is made available for usage in the expression by passing it in the constructor. The `getExpression()` method contains the logic of the condition, expressed using the variables and functions available in the expression engine. The expression must evaluate to `true` or `false`, indicating whether the condition is met. The example uses three expressions: - the custom `is_anniversary()` function, returning a value indicating whether today is user's registration anniversary - the custom `current_user_registration_date` variable, holding the value of current user's registration date - the custom `tolerance` variable, holding the acceptable tolerance (in days) for the calculation For each custom condition class, you must create a dedicated condition factory, a class implementing the `\Ibexa\Discounts\Repository\DiscountCondition\DiscountConditionFactoryInterface` interface. This allows you to create conditions when working in the context of the Symfony service container. ``` 100, 'germany' => 81.6, 'france' => 80, 'spain' => 69, ]; /** @param ?array $powerParityMap */ public function __construct(?array $powerParityMap = null) { parent::__construct( [ 'power_parity_map' => $powerParityMap ?? self::DEFAULT_PARITY_MAP, ] ); } /** @return array */ public function getMap(): array { return $this->getExpressionValue('power_parity_map'); } public function getExpression(): string { return 'amount * (power_parity_map[get_current_region().getIdentifier()] / power_parity_map["default"])'; } public function getType(): string { return self::TYPE; } } ``` The `getExpression()` method contains the logic of the rule, expressed using the variables and functions available in the expression engine. The expression must return the new price of the product. It uses three expressions: - the built-in `amount` variable, holding the purchase amount - the built-in `get_current_region()` function, returning the current region - a custom `power_parity_map` variable, holding the purchasing power parity map. It's defined in the constructor As with conditions, create a dedicated rule factory: ``` inner->getOrder() ); } } ``` ``` App\Discounts\RecentDiscountPrioritizationStrategy: decorates: Ibexa\Contracts\Discounts\DiscountPrioritizationStrategyInterface arguments: $inner: '@.inner' ``` # Extend Discounts wizard Editions: LTS Update, Commerce ## Introduction For the store managers to use your [custom conditions and rules](https://doc.ibexa.co/en/latest/discounts/extend_discounts/#implement-custom-condition), you need to integrate them into the back office discounts creation form. This form is built using [Symfony Forms](https://symfony.com/doc/7.4/forms.html) and the [`DiscountFormMapperInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountFormMapperInterface.html) interface is at the core of the implementation. It provides a two-way mapping between the form structures (used to render the form) and the PHP API values used to create the discounts by offering methods related to: - form rendering - data structure mapping Form rendering methods return objects implementing the [`DiscountDataInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-Form-Data-DiscountDataInterface.html), allowing you to access and modify the form data. They include: - [`createFormData()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountFormMapperInterface.html#method_createFormData) renders the form before the discount is created - [`mapDiscountToFormData()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountFormMapperInterface.html#method_mapDiscountToFormData) renders the form when the discount already exists. It fills the discount edit form with the saved discount details The data mapping methods are responsible for transforming the form data into structures compatible with the [Discount's PHP API](https://doc.ibexa.co/en/latest/discounts/discounts_api/index.md) services like [`DiscountServiceInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountServiceInterface.html) and [`DiscountCodeServiceInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-DiscountsCodes-DiscountCodeServiceInterface.html). They include: - [`mapCreateDataToStruct()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountFormMapperInterface.html#method_mapCreateDataToStruct) creates the [`DiscountCreateStruct`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Struct-DiscountCreateStruct.html) object to create the discount - [`mapUpdateDataToStruct()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountFormMapperInterface.html#method_mapUpdateDataToStruct) creates the [`DiscountUpdateStruct`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Struct-DiscountUpdateStruct.html) object to update the discount - [`mapEditTranslateDataToStruct()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountFormMapperInterface.html#method_mapEditTranslateDataToStruct) creates the [`TranslationStruct`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Struct-DiscountTranslationStruct.html) objects for [translating the discounts](https://doc.ibexa.co/en/latest/discounts/discounts_api/#discount-translations) In the UI, the discounts wizard consists of several steps: - General properties - Target group - Products - Conditions (only for Cart discounts) - Discount value - Summary Each of these steps is represented by its own form mappers, data classes, and form types in the code. In addition, the main form mapper and the form mappers responsible for each step in the wizard dispatch events that you can use to add your custom logic. See [discount's form events](https://doc.ibexa.co/en/latest/api/event_reference/discounts_events/#form-events) for a list of the available events. ## Integrate custom conditions This example continues the [anniversary discount condition example](https://doc.ibexa.co/en/latest/discounts/extend_discounts/#implement-custom-condition), integrating the condition with the wizard by adding a dedicated step with condition options. The example limits the new step to cart discounts only. To add a custom step, create a value object representing the step. It contains the step identifier, properties for storing form data, and extends the [`AbstractDiscountStep`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-Form-Data-AbstractDiscountStep.html): ``` 'addAnniversaryConditionStep', MapDiscountToFormDataEvent::class => 'addAnniversaryConditionStep', ]; } /** * @param \Ibexa\Contracts\Discounts\Event\CreateFormDataEvent|\Ibexa\Contracts\Discounts\Event\MapDiscountToFormDataEvent $event */ public function addAnniversaryConditionStep(Event $event): void { $data = $event->getData(); if ($data->getType() !== DiscountType::CART) { return; } /** @var \App\Discounts\Condition\IsAccountAnniversary $discount */ $discount = $event instanceof MapDiscountToFormDataEvent ? $event->getDiscount()->getConditionByIdentifier(IsAccountAnniversary::IDENTIFIER) : null; $conditionStep = $discount !== null ? new AnniversaryConditionStep(true, $discount->getTolerance()) : new AnniversaryConditionStep(); $event->setData( $event->getData()->withStep( $conditionStep, AnniversaryConditionStep::IDENTIFIER, 'Anniversary Condition', -45 // Priority ) ); } } ``` Attaching the `addAnniversaryConditionStep()` method to both these events adds the custom step both in discount creation and edit forms. The method first verifies if the form renders the cart discount wizard, according to assumptions of this example. Then, it creates the `AnniversaryConditionStep` object. If the discount existed already and is being edited, the saved values are used to populate the form. Finally, the new step is added to the wizard using the `withStep()` method, using `45` as step priority. Each of the existing form steps has its own priority, allowing you to add your custom steps between them. | Step name | Priority | | ------------------ | -------- | | General properties | 50 | | Target group | -20 | | Products | -30 | | Conditions | -40 | | Discount value | -50 | | Summary | -1000 | The custom step is added between the "Conditions" and "Discount value" steps. To add form fields to it, create an event listener adding your fields and a custom form type: ``` getStepData() instanceof AnniversaryConditionStep; } public function addFields(FormInterface $form, DiscountStepData $data, PreSetDataEvent $event): void { $form->add( 'stepData', AnniversaryConditionStepType::class, [ 'label' => false, ] ); } public static function getTranslationMessages(): array { return [ (new Message('discount.step.custom.label', 'discount'))->setDesc('Custom'), ]; } } ``` ``` */ final class AnniversaryConditionStepType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options): void { $builder->add( 'enabled', CheckboxType::class, [ 'label' => 'Enable anniversary discount', 'required' => false, ] )->add( 'tolerance', NumberType::class, [ 'label' => 'Tolerance in days', 'required' => false, ] ); } public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'data_class' => AnniversaryConditionStep::class, ]); } } ``` The new form step, including its form fields, are now part of the discounts wizard. The last task is making sure that the form data is correctly saved by attaching it to the discounts API structs. Expand the previously created `AnniversaryConditionStepEventSubscriber` to listen to two additional events: - [`CreateDiscountCreateStructEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-CreateDiscountCreateStructEvent.html) - [`CreateDiscountUpdateStructEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-CreateDiscountUpdateStructEvent.html) and add the `addStepDataToStruct()` method: ``` 'addAnniversaryConditionStep', MapDiscountToFormDataEvent::class => 'addAnniversaryConditionStep', CreateDiscountCreateStructEvent::class => 'addStepDataToStruct', CreateDiscountUpdateStructEvent::class => 'addStepDataToStruct', ]; } /** * @param \Ibexa\Contracts\Discounts\Event\CreateFormDataEvent|\Ibexa\Contracts\Discounts\Event\MapDiscountToFormDataEvent $event */ public function addAnniversaryConditionStep(Event $event): void { $data = $event->getData(); if ($data->getType() !== DiscountType::CART) { return; } /** @var \App\Discounts\Condition\IsAccountAnniversary $discount */ $discount = $event instanceof MapDiscountToFormDataEvent ? $event->getDiscount()->getConditionByIdentifier(IsAccountAnniversary::IDENTIFIER) : null; $conditionStep = $discount !== null ? new AnniversaryConditionStep(true, $discount->getTolerance()) : new AnniversaryConditionStep(); $event->setData( $event->getData()->withStep( $conditionStep, AnniversaryConditionStep::IDENTIFIER, 'Anniversary Condition', -45 // Priority ) ); } public function addStepDataToStruct(DiscountStructEventInterface $event): void { /** @var AnniversaryConditionStep $stepData */ $stepData = $event ->getData() ->getStepByIdentifier(AnniversaryConditionStep::IDENTIFIER)?->getStepData(); if ($stepData === null || !$stepData->enabled) { return; } $discountStruct = $event->getStruct(); $discountStruct->addCondition(new IsAccountAnniversary($stepData->tolerance)); } } ``` When the form is submitted, this method extracts information whether the store manager enabled the anniversary discount in the form and adds the condition to make sure this data is properly saved. The custom condition is now integrated with the discounts wizard and can be used by store managers to attract new customers. ## Integrate custom rules This example continues the [purchasing power parity rule example](https://doc.ibexa.co/en/latest/discounts/extend_discounts/#implement-custom-rules), integrating the rule with the wizard. First, create a new service implementing the `DiscountValueMapperInterface` interface, responsible for handling the new rule type: ``` getRule(); if (!$discountRule instanceof PurchasingPowerParityRule) { throw new LogicException('Not implemented'); } return new PurchasingPowerParityValue(); } public function mapCreateDataToStruct( DiscountValueInterface $data, DiscountCreateStruct $struct ): void { $this->addRuleToStruct($data, $struct); } public function mapUpdateDataToStruct( DiscountInterface $discount, DiscountValueInterface $data, DiscountUpdateStruct $struct ): void { $this->addRuleToStruct($data, $struct); } /** * @param \Ibexa\Contracts\Discounts\Value\Struct\DiscountCreateStruct|\Ibexa\Contracts\Discounts\Value\Struct\DiscountUpdateStruct $struct */ private function addRuleToStruct(DiscountValueInterface $data, $struct): void { if (!$data instanceof PurchasingPowerParityValue) { throw new LogicException('Not implemented'); } $rule = new PurchasingPowerParityRule(); $struct->setRule($rule); } } ``` It uses an `PurchasingPowerParityValue` object to store the form data: ``` setDesc('Regional'), ]; } } ``` Link them together when defining the services: ``` App\Form\FormMapper\PurchasingPowerParityValueMapper: ~ App\Form\FormMapper\PurchasingPowerParityFormMapper: arguments: $discountValueMapper: '@App\Form\FormMapper\PurchasingPowerParityValueMapper' ``` The [`DiscountFormMapperInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountFormMapperInterface.html) acts as a registry, finding a form mapper dedicated for given rule type and delegating to the responsibility of building the form. As each rule type might have a different rule calculation logic, each rule must have a different "Discount value" step in the form. To create it, create a dedicated class implementing the [`DiscountValueFormTypeMapperInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-Form-DiscountValueFormTypeMapperInterface.html) ``` */ final class PurchasingPowerParityValueType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options): void { $availableRegionHandler = static function (FormInterface $form, PurchasingPowerParityValue $data): void { $regions = $data->getDiscountData()->getGeneralProperties()->getRegions(); $regionNames = implode(', ', array_map(static fn (RegionInterface $region): string => $region->getIdentifier(), $regions)); $options = [ 'required' => false, 'disabled' => true, 'label' => 'This discount applies to the following regions', 'data' => $regionNames, ]; $form->add('value', TextType::class, $options); }; $builder->add('type', FormType::class, [ 'mapped' => false, 'label' => false, ]); $builder->addEventListener( FormEvents::PRE_SET_DATA, static function (PreSetDataEvent $event) use ($availableRegionHandler): void { $form = $event->getForm(); $availableRegionHandler($form, $event->getData()); }, ); $builder->get('type')->addEventListener( FormEvents::POST_SUBMIT, static function (PostSubmitEvent $event) use ($availableRegionHandler): void { $form = $event->getForm()->getParent(); assert($form !== null); $availableRegionHandler($form, $form->getData()); }, ); } #[\Override] public function getParent(): string { return DiscountValueType::class; } public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'data_class' => PurchasingPowerParityValue::class, ]); } } ``` In the example above, the discount value step is used to display a read-only field with regions the discount is limited to. The `$availableRegionHandler` callback function extracts the selected regions and modifies the form as needed, using the `FormEvents::PRE_SET_DATA` and `FormEvents::POST_SUBMIT` events. The last step consists of providing all the required translations. Specify them in `translations/ibexa_discount.en.yaml`: ``` ibexa.discount.type.purchasing_power_parity: Purchasing Power Parity discount.rule_type.purchasing_power_parity: Purchasing Power Parity ``` The custom rule is now integrated with the discounts wizard and can be used by store managers to offer new discounts. # Customer management # Customer Portal Editions: Experience A Customer Portal serves as a central entry point to your services and products. It helps you provide a unique user experience with a single point of access to any relevant self-service options for your products and services. Ibexa DXP Customer Portal and customer management that ships with it let you create and handle business accounts and communicate with your partners in a personalized space. With this feature, your customers can self-register, edit their organization information, invite and view members, check their order history, and more. - [Customer Portal product guide](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/customer_management/customer_portal_guide/): Check all the capabilities and advantages that the Customer Portal offers to the clients by reading the Customer Portal product guide. - [Customer Portal configuration](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/customer_management/cp_configuration/): Configure Customer Portal to fit the needs of your business. - [Customer Portal applications](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/customer_management/cp_applications/): Customization of an approval process for new companies applications. - [Inviting users](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/users/invitations/): Manage user invitations to create an account in the frontend or the back office. - [Create Customer Portal](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/customer_management/cp_page_builder/): Create unique Customer Portals for your clients with Page Builder. - [Create user registration form](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/customer_management/create_user_registration_form/): Customize the registration form for new users in your site front end. # Customer Portal product guide Editions: Experience ## What is Customer Portal A Customer Portal serves as a central entry point to your services and products. It helps you provide a unique user experience with a single point of access to any relevant self-service options for your products and services. Ibexa DXP Customer Portal and customer management that ships with it let you create and handle business accounts and communicate with your partners in a personalized space. With this feature, your customers can self-register, edit their organization information, invite and view members, check their order history, and more. ## Availability Customer Portal is available in Ibexa Experience. It's also compatible with PIM, Commerce and Ibexa Connect. ## How does Customer Portal work? Customer Portal is a component based on content types. This means that Ibexa DXP provides containers, user management, content management, so you can focus on business logic and general outlook of the portal for your B2B clients. ### Customer Portal The Customer Portal allows company members to log in and manage their profiles and order history. With user differentiation, company buyers can only purchase products while company admins can invite and manage members and change company information, such as billing addresses. *[Image: Customer Portal dashboard]* ### Editable in Page Builder Custom Customer Portal can be created and edited in Page Builder to meet the needs of each business type, company, or market they operate on. To create a new Customer Portal, go to **Content** and, from the menu, select **Content structure**. There, navigate to the root container for your Customer Portals and select **Customer Portal Page**. In the Page Builder creation box, you see the Customer Portal layout where you can add a dedicated Customer Portal block, Sales Representative, or choose from a selection of blocks available to your Ibexa DXP version. If the built-in page blocks aren't sufficient to fulfill your needs, you can add your own. *[Image: Editable in Page Builder]* You can allow company members to see multiple versions of Customer Portal on a single page by adding them under one Customer Portal container and combining SiteAccess matchers. This setup is recommended for global markets or company-specific portals, where each portal is designed specifically for its customers and their needs. *[Image: Multiple portals]* ### Company management The main company management takes place in the back office where each company has its own profile where sales representative can find: - summary with basic information and order history - company profile with billing information and contact person - list of members and pending invitations - address book with multiple shipping addresses *[Image: Companies section in back office]* From there, they can activate and deactivate the company, edit its information, invite members, manage their roles, and edit their basic information. In the roles section, you can define policies for each user group, for example, a Company buyer. You can also set up policies for every user who has a business account by editing a Corporate Access role. ### Members Company members aren't standard users. They belong to a separate category called Corporate Accounts. This category is located in **Admin** -> **Corporate** -> **Corporate Accounts**. There, you can find a list of companies and their members. This feature comes with a set of new roles: - Member — users who are members of a company - Corporate Access — users who can log into Customer Portal - Company Admin — users who can edit company's details - Company Buyer — users who can buy in company's name All roles and policies associated with them can be fully customized to fit your business needs. ### Invitations Members can be invited to the organization from: - the back office: go to **Customers** -> **Companies** -> **Select your company** -> **Invitations** -> **Invite member** - the Customer Portal: go to your company admin profile, select **Members** -> **Invite members** Then, in a pop-up fill out email addresses one by one, or use drag and drop to upload a file with a list of emails. You also have to assign a role to each new member from a drop-down list. Click **Send** to send out invitations. *[Image: Invitations]* Invited users receive an email message with a registration link. With it, they can register and create their account in the Customer Portal. *[Image: Create account]* ### Company self-registration Self-registration allows business customers to take charge and apply for a business account by themselves. Applications go through the approval process in the back office where they can be accepted, rejected or put on hold. If they're accepted, the business partner receives an invitation link to the Customer Portal, where they can set up their team and manage their account. To apply for a business account, a company needs to provide their basic information, contact information and billing address in an application. *[Image: Company self-registration]* The approval process is customizable. You can decide which user has approval rights by granting them `Company Application/Workflow` policy, you can also decide between which states the user may move applications: - on hold - accept - reject If built-in statuses aren't sufficient, you can add custom ones. You can also edit or add reasons for not accepting the company application. Finally, you can customize the registration site itself. ### REST API Customer Portal comes with [REST API](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Corporate-Account) for interacting with corporate accounts from the context of the Ibexa Connect app. ## Capabilities ### Company management Sales representatives can manage details for companies they're associated with, such as contact persons, billing addresses, and more by accessing back office. Company admins are also able to manage the company's details in the Customer Portal interface. By giving users power to manage their own accounts, you reduce the need for administrative interventions. ### Self registration Self-registration allows your customers to take control of their business accounts. This not only improves customer satisfaction but also reduces the administrative burden on your team. With the ability to integrate with Ibexa Connect, you're able to fully automate the process. ### Address book Use of an address book allows you to add many shipping addresses to one company for clients with multiple locations. ### Custom prices You can offer special prices and additional discounts dedicated for Customer groups containing company members with verified accounts. ### Available in segments Corporate accounts are available in segments, which means you can assign companies to different recommendation groups based on gathered data, and deliver recommendations. It allows you to make use of customer targeting of the segments and create personalized experience for each company. ## Benefits ### General overview The overall benefit of customer portals is the help they provide to retain customers and increase loyalty, while freeing up customer service employees time for higher-level work. They can achieve that by providing customers with up-to-date information about their orders and deliveries, personalize shopping experience, offer special deals available only to B2B partners, and do that in one, accessible space. Currently, Customer Portals are a standard in global sites such as Amazon. They're the level of quality that customers expect, and all businesses strive for. ### Simplified shopping process Business account helps streamline the B2B shopping process with all the paperwork, payment, and other administrative tasks converted into a few steps with prefilled forms, billing addresses, shipping addresses, and more. Making your site a go-to place for company orders. ### Better customer experience In the era of internet, customers expect quick, accessible and excellent quality service, and user experience from every business they associate with. Customer portals offer a seamless self-service experience by providing complete 24/7 access to relevant, up-to-date information and customer support. ### Client encouragement Price strategies are a great way to build and maintain strong relationships with your trading partners. With special prices available to B2B clients, you can offer the best deals in highly competitive markets. Those discounts may be a great encouragement to convince big buyers to choose your business over other options. Competitive prices impact not only the size of the customer base, they affect every customer’s purchasing strategy, including the diversity, frequency, and volume of their orders. ### Cost benefits Customer portals help you to automate tasks that otherwise would be done by your employees manually, such as customer services, checking shipment status. An additional benefit of customer portals is their availability 24/7. Thus, reducing the need to allocate resources to extend working hours or hire more employees. ### Localization and personalization The use of Page Builder in the Customer Portal creation process enables you to create unique experiences for each business customer based on their location, business type, company, or market they operate on. # Customer Portal configuration Editions: Experience You can overwrite the default configuration of the Customer Portal to fit its capabilities to the unique needs of your business. ## `corporate` SiteAccess The predefined `corporate` SiteAccess in `corporate_group` (configured in `config/packages/ibexa.yaml`) serves the Customer Portal. If you need a multisite setup with multiple Customer Portals, add any additional SiteAccesses to `corporate_group`. ## Customer identifier `ibexa_default_settings.yaml` contains a setting that indicates what content types should be treated like Users in terms of, for example, usage in `UserService`: ``` ibexa: system: default: user_content_type_identifier: ['user', 'customer'] ``` ## Roles and policies You can add custom roles to your installation by listing them under the `ibexa.site_access.config.default.corporate_accounts.roles` [configuration](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files). This key overwrites the default list set in `vendor/ibexa/corporate-account/src/bundle/Resources/config/default_settings.yaml` (the following example redeclares them for clarity): ``` parameters: ibexa.site_access.config.default.corporate_accounts.roles: admin: Company Admin buyer: Company Buyer custom_role: Company Assistant ``` You can do it per SiteAccess or SiteAccess group by using [SiteAccess-aware configuration](https://doc.ibexa.co/en/latest/multisite/siteaccess/siteaccess_aware_configuration/index.md). ## Content type names You can change names of default content types by assigning what content types should be used to describe `Company` and `Member` in the back office. Proceed only if you already have a `Company` content type in your system, and you don't want to change its identifier. Configuration for content type names is placed under the `ibexa_corporate_account` key, like shown in `Ibexa\Bundle\CorporateAccount\DependencyInjection\Configuration`. To change content type names, adjust corporate account configuration in the following way: ``` ibexa_corporate_account: content_type_mappings: company: your_ct_identifier ``` > **Caution: Migration** > > If you decide to change deafult names of content types, during migration you have to adjust files accordingly. ## Registration You can define what fields are required in the Customer Portal registration form. To do so, [create and configure user registration form](https://doc.ibexa.co/en/latest/customer_management/create_user_registration_form/index.md). ## Address With the Address field type, you can customize address fields and configure them per country. To learn more, see [Address field type documentation](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/addressfield/index.md). ## Templates You can also define new templates for, among others: invitation email, reset password message and the information screens after any of the user's actions. ``` ibexa: system: site_group: content_view: full: confirmation_page: template: "@@ibexadesign/customer_portal/account/forgot_password/confirmation_page.html.twig" match: Identifier\ContentType: confirmation_page ``` ## Order management Reviewing pending and past orders in Customer Portal requires that you configure all currencies that any of the customers may use under the `ibexa.system..product_catalog.currencies` key. The first currency from the list is then used for filtering the orders list and calculating the **Average order** and **Total amount** values. For more information, see [Enable purchasing products](https://doc.ibexa.co/en/latest/product_catalog/enable_purchasing_products/index.md). # Create Customer Portal Editions: Experience On this page, you can learn how to configure the Customer Portal feature to be editable with Page Builder. If you already configured Customer Portal, you can learn how to build it with a Page Builder in [User Documentation](https://doc.ibexa.co/projects/userguide/en/5.0/customer_management/build_customer_portal/). First, you need to decide if you want to create and configure [one portal](#create-and-configure-one-portal) or [multiple portals](#create-and-configure-multiple-portals) setup. ## Create and configure one portal This setup is recommended for use cases with one Customer Portal for all markets. If you plan to expand your portal portfolio in the future, see [multiple portal configuration](#create-and-configure-multiple-portals). ### Configure Page Builder access to Customer Portal First, create a Customer Portal page, its location ID needs to be later specified in the configuration. To do it, go to **Content** -> **Content structure**, and select **Customer Portal Page**. For now, you only need to add a name and a description in the field view, you can find it in the upper toolbar on the left side. Next, click **Publish** to see the page in the content tree. *[Image: Add name and description to Customer Portal]* To be able to see the Customer Portal site template in the Page Builder you need to add `custom_portal` SiteAccess to the configuration. First, under the `ibexa.siteaccess` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files) add `custom_portal` to the SiteAccess `list` and to `corporate_group`. Next, add configuration for `corporate_group` and `custom_portal` under `ibexa.system`. Remember to specify `location_id` of your Customer Portal, you can find it under the **Technical details** tab of your new page. ``` ibexa: siteaccess: list: - import - site - admin - corporate - custom_portal groups: site_group: [import, site] storefront_group: [site] corporate_group: [corporate, custom_portal] system: corporate_group: languages: [eng-GB] custom_portal: languages: [ eng-GB ] content: tree_root: location_id: location_id_of_customer_portal excluded_uri_prefixes: [ /media/, /images/ ] ``` Next, under the `ibexa.system.admin.page_builder` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files), add `custom_portal` to [the SiteAccess list available to Page Builder](https://doc.ibexa.co/en/latest/multisite/multisite_configuration/#siteaccesses-and-page-builder): ``` ibexa: system: admin: page_builder: siteaccess_list: - site - corporate - custom_portal ``` Now, you can go to your Customer Portal landing page and edit it in Page Builder. *[Image: Edit Customer Portal in Page Builder]* ### Grant permissions to customers You need to grant the following permissions to company members, so they can view custom Customer Portal: - `user/login` to `custom_portal` SiteAccess - `content/read` to the Customer Portal *[Image: Single Customer Portal permissions]* If members of the company don't have sufficient permissions for any Customer Portal, they're redirected to the default Customer Portal view. > **Note: Note** > > Customer Portal is only available to users that are members of the company. Even if a user has all the sufficient permissions but isn't a member of a company, this user cannot see the Customer Portal. ## Create and configure multiple portals This setup is recommended for global markets or company specific portals, where each portal is design specifically for its users and their needs. ### Customer Portal container First, you need to create a root folder for Customer Portals, its location ID needs to be later specified in the configuration as [a tree root](https://doc.ibexa.co/en/latest/multisite/multisite_configuration/#location-tree). To do it, go to **Content** -> **Content structure**, and select **Create content**. There you can see two possibilities **Customer Portal** and **Customer Portal Page**. *[Image: Create content tab]* The first one is a separate content type used as a container for your Customer Portal pages. Customer Portals containers should be used to sort Customer Portal pages and any other content types used by them, such as articles, inside the root folder. It's recommended that you use them instead of folders to divide and store your portals. Select **Customer Portal**, define its name and publish. *[Image: Customer Portals folder]* ### Configure Page Builder access to Customer Portal To be able to see Customer Portal site template in the Page Builder you need to add `custom_portal` SiteAccess to the configuration. First, under the `ibexa.siteaccess` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files), add `custom_portal` to the SiteAccess `list` and to `corporate_group`. Next, add configuration for `corporate_group` and `custom_portal` under `ibexa.system`. Remember to specify `location_id` of the root folder for Customer Portals, you can find it under the **Technical details** tab. ``` ibexa: siteaccess: list: - import - site - admin - corporate - custom_portal groups: site_group: [import, site] storefront_group: [site] corporate_group: [corporate, custom_portal] system: corporate_group: languages: [eng-GB] custom_portal: languages: [ eng-GB ] content: tree_root: location_id: location_id_of_customer_portals_root_folder excluded_uri_prefixes: [ /media/, /images/ ] ``` Next, under the `ibexa.system.admin.page_builder` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files), add `custom_portal` to [the SiteAccess list available to Page Builder](https://doc.ibexa.co/en/latest/multisite/multisite_configuration/#siteaccesses-and-page-builder): ``` ibexa: system: admin: page_builder: siteaccess_list: - site - corporate - custom_portal ``` Now, you can go back to your Customer Portal's container. All landing pages that you create in it use Customer Portal template. ### Assign portal to Customer group You can assign multiple Customer Portal containers or Pages to a specific Customer group. First, you need to grant the following permissions to company members from the Customer group: - `user/login` to `custom_portal` SiteAccess - `content/read` to selected Customer Portals *[Image: Customer Portal permissions]* If members of the Customer group don't have sufficient permissions for any Customer Portal assigned to them, they're redirected to the default Customer Portal view. > **Note: Note** > > Customer Portal is only available to users that are members of the company. Even if user has all the sufficient permissions but isn't a member of a company, this user cannot see the Customer Portal. #### Build-in portal mapping Now, you need to assign your custom portals to Customer groups. Add portal mapping configuration in `config/services.yaml`: ``` parameters: ibexa.corporate_account.customer_portal.customer_group_to_portal_map: eu: - 6bd4c938f9b3f668057c7e20987fac6c - 7bf85988a77ee859f2466av2b42bd909 us: - 6ce85480aeaeed59f7431a12b46bc869 ``` There, you can specify which Customer Portals should be available to which Customer group by adding: - Customer group identifier. You can find it in the **Summary** section of the Company. - Location remote ID of Customer Portal container or Customer Portal page. You can find it in the **Details** section. Portals are displayed to the Customer group in order specified in the configuration based on company member's permissions. #### Custom portal mapping You can specify your own custom logic for redirecting members to a specific Customer Portal. To do so, implement `\Ibexa\Contracts\CorporateAccount\CustomerPortal\PickRule\CustomerPortalPickRule` and tag it with `ibexa.corporate_account.customer_portal.pick_rule`. ### Multiple portals on single page You can allow company members to see multiple versions of Customer Portal on a single page by [combining SiteAccess matchers](https://doc.ibexa.co/en/latest/multisite/siteaccess/siteaccess_matching/#custom-matchers) with `Compound\LogicalAnd`: ``` ibexa: siteaccess: match: Compound\LogicalAnd: custom_portal: matchers: Map\Port: eu: true Map\Host: example.com: true match: custom_portal Map\Host: admin.example.com: site_admin ``` *[Image: Multiple portals in one view]* ## Change Customer Portal layout You can change Customer Portal layout by adding your custom template under `ibexa.system..page_layout`: ``` ibexa: system: custom_portal: languages: [ eng-GB ] page_layout: "@App/my_page_layout.html.twig" content: tree_root: location_id: location_id_of_customer_portals_root_folder excluded_uri_prefixes: [ /media/, /images/ ] ``` To generate the Customer Portal menu you should use `customer_portal.menu.main` key: ``` {% block side_column %}
    {% block left_sidebar %} {% set main_menu = knp_menu_get('customer_portal.menu.main', [], {}) %} {{ knp_menu_render(main_menu, { depth: 1, template: '@ibexadesign/customer_portal/menu.html.twig', currentClass: 'active', ancestorClass: 'active', }) }} {% endblock %}
    {% endblock %} ``` To learn more about creating a menu, see [Add navigation menu](https://doc.ibexa.co/en/latest/templating/layout/add_navigation_menu/index.md). # Customer Portal applications Editions: Experience New business customers can apply for a company account. Applications go through the approval process in the back office where they can be accepted, rejected or put on hold. If they're accepted, the business partner receives an invitation link to the Customer Portal, where they can set up their team and manage their account. For more information on company self-registration, see [user guide documentation](https://doc.ibexa.co/projects/userguide/en/5.0/customer_management/company_self_registration/). If provided options are too limited, you can customize an approval process by yourself. ## Roles and policies Any user can become application approver, as long as they have the `Company Application/Workflow` policy assigned to their role. There, you can define between which states the user may move applications. For example, the assistant can put new applications on hold, or reject them, and only the manager can accept them. *[Image: Company Application policy]* ## Customer Portal application configuration Below, you can find possible configurations for Customer Portal applications. ### Reasons for rejecting application To change or add reasons for not accepting Corporate Portal application go to `vendor/ibexa/corporate-account/src/bundle/Resources/config/default_settings.yaml`. ``` parameters: ibexa.site_access.config.default.corporate_accounts.reasons: reject: [Malicious intent / Spam] on_hold: [Verification in progress] ``` ### Timeout Registration form locks for 5 minutes after unsuccessful registration, if the user, for example, tried to use an email address that already exists in a Customer Portal clients database. To change that duration, go to `config/packages/ibexa.yaml`. ``` framework: rate_limiter: corporate_account_application: policy: 'fixed_window' limit: 1 interval: '5 minutes' lock_factory: 'lock.corporate_account_application.factory' ``` ## Customization of an approval process In this procedure, you add a new status to the approval process of business account application. ### Add new status First, under the `ibexa.system..corporate_accounts.application.states` add a `verify` status to the [configuration](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa: system: default: corporate_accounts: application: states: [ 'new', 'accept', 'on_hold', 'reject', 'verify' ] ``` ### Create new Form Type Next, create a new form type in `src/Form/VerifyType.php`. It's displayed in the application review stage. ``` add(self::FIELD_APPLICATION, HiddenType::class) ->add('new_field', TextType::class) ->add(self::FIELD_NOTES, TextareaType::class, [ 'required' => false, ]) ->add(self::FIELD_VERIFY, SubmitType::class); } } ``` Line 29 defines where the form should be displayed, line 21 adds **Note** field, and line 22 adds the **Verify** button. ### Create event subscriber to pass the form Add an event subscriber that passes a new form type to the frontend. Create `src/Corporate/EventSubscriber/ApplicationDetailsViewSubscriber.php` following the example below: ``` getApplication(); $view->addParameters([ 'verify_form' => $this->formFactory->create( VerifyType::class, [ 'application' => $application->getId(), ] )->createView(), ]); } protected function supports(View $view): bool { return $view instanceof ApplicationDetailsView; } } ``` In line 39, you can see the `verify_form` parameter that passes the `verify` form to the application review view. ### Add form template To be able to see the changes you need to add a new template `templates/themes/admin/corporate_account/application/details.html.twig`. ``` {% extends "@IbexaCorporateAccount/themes/admin/corporate_account/application/details.html.twig" %} {% block content %} {{ form_start(verify_form, { action: path('ibexa.corporate_account.application.workflow.state', { state: 'verify', applicationId: application.id, }), method: 'POST'}) }} {{ form_row(verify_form.notes) }}
    {{ form_widget(verify_form.verify, { attr: { class: 'ibexa-btn ibexa-btn--primary ibexa-ca-application-workflow-extra-actions__btn', }}) }}
    {{ form_end(verify_form) }} {{ parent() }} {% endblock %} ``` It overrides the default view and adds a **Verify** button to the review view. To check the progress, go to **Members** -> **Applications**. Select one application from the list and inspect application review view for a new button. *[Image: Verify button]* ### Create event subscriber to verify state Now, you need to pass the information that the button has been selected to the list of applications to change the application status. Create another event subscriber that passes the information from the created form to the application list `src/Corporate/EventSubscriber/VerifyStateEventSubscriber.php`. ``` 'mapApplicationWorkflowForm', ApplicationWorkflowEvents::getStateEvent(self::VERIFY_STATE) => 'applicationVerify', ]; } public function mapApplicationWorkflowForm(MapApplicationWorkflowFormEvent $event): void { if ($event->getState() === self::VERIFY_STATE) { $form = $this->formFactory->create(VerifyType::class, $event->getData()); $event->setForm($form); } } public function applicationVerify(ApplicationWorkflowFormEvent $event): void { $data = $event->getData(); if (!is_array($data)) { return; } $applicationStateUpdateStruct = new ApplicationStateUpdateStruct($event->getApplicationState()->getId()); $applicationStateUpdateStruct->state = self::VERIFY_STATE; $this->applicationStateHandler->update($applicationStateUpdateStruct); $this->notificationHandler->success( /** @Desc("Application moved to Verification state") */ 'application.state.verify.notification', [], 'corporate_account_application' ); } } ``` In line 46, you can see that it handles changes to verify status. The subscriber only informs that the status has been changed (line 72). Now, if you click the **Verify** button during application review, the application gets **Verify** status. *[Image: Verify status]* # Create user registration form You can create a registration form for users to your website by creating a Twig template or editing the existing registration form in YAML file. Follow the instructions below to create and customize templates for a registration form, and a registration confirmation page. First, make sure you [enabled user registration](https://doc.ibexa.co/en/latest/permissions/permission_use_cases/#register-users). ## Configure existing form In your configuration, under `allowed_field_definitions_identifiers` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files), specify the fields that should be part of your registration form. You can also define what kind of user you want to create under `user_type_identifier`, for example, frontend user. To learn more about available users, see [user types documentation](https://doc.ibexa.co/en/latest/users/user_registration/#user-types). ``` ibexa: system: default: user_registration: user_type_identifier: customer form: allowed_field_definitions_identifiers: - first_name - last_name - user_account ``` ## Add a form template Add the following configuration under the `ibexa.system..user_registration` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa: system: default: user_registration: templates: form: '@ibexadesign/user/registration_form.html.twig' confirmation: '@ibexadesign/user/registration_confirmation.html.twig' ``` This defines which templates are used for rendering the registration form and confirmation page. In the `templates/themes//user/registration_form.html.twig` create the template for registration form. Example registration form: ``` {% extends no_layout is defined and no_layout == true ? view_base_layout : page_layout %} {% block content %}
    {{ form_start(form) }} {% for fieldForm in form.fieldsData %} {% set fieldIdentifier = fieldForm.vars.data.fieldDefinition.identifier %}
    {{ form_widget(fieldForm.value, { 'contentData': form.vars.data }) }}
    {%- do fieldForm.setRendered() -%} {% endfor %}
    {{ form_widget(form.register, {'attr': { 'class': 'btn btn-block btn-primary' }}) }}
    {{ form_end(form) }}
    {% endblock %} ``` In the `templates/themes//user/registration_confirmation.html.twig`, create the template for confirmation form. Example confirmation form: ``` {% extends no_layout is defined and no_layout == true ? view_base_layout : page_layout %} {% block content %}

    Your account has been created

    Thank you for registering an account. You can now login.

    {% endblock %} ``` To add a link redirecting to the login form, in the page layout template, provide the following code: ``` Register ``` # Ibexa Engage # Ibexa Engage Editions: Experience Ibexa Engage is a data collection tool. It gives you the ability to use the [Qualifio](https://qualifio.com/) tools to engage your audiences. You can use interactive content to build relationships and collect important data, for example, a list of recent orders, or personal information about customers. You can also integrate Ibexa Engage with Ibexa Connect to create workflows. - [Ibexa Engage](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/ibexa_engage/install_ibexa_engage/): Install and configure Ibexa Engage. - [Create campaign](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/ibexa_engage/create_campaign/): Create campaign with Ibexa Engage. - [Use Ibexa Connect](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/ibexa_engage/integrate_ibexa_connect/): Integrate Ibexa Engage with Ibexa Connect. # Ibexa Engage Editions: Experience Ibexa Engage is a data collection tool. It enables you to engage your audiences by using the [Qualifio](https://qualifio.com/) tools. You can use interactive content to gather valuable data, for example, customer data or recent orders list, and create connections. For more information, see [Qualifio Developers documentation](https://developers.qualifio.com/docs/engage/). ## Enable Ibexa Engage account To use Ibexa Engage, you must make arrangements with Ibexa DXP to define the initial configuration. Ibexa team creates and provides user account. An invitation link is sent during the setup process. For more information, see [Ibexa Engage in User Documentation](https://doc.ibexa.co/projects/userguide/en/5.0/ibexa_engage/ibexa_engage/#request-access). ## Install Ibexa Engage Ibexa Engage comes from v4.6.6 of Ibexa Experience. If you have different version, run the following command to install the bundle: ``` composer require ibexa/engage ``` You can check for its presence by using the following command: ``` composer show | grep "ibexa/engage" ``` This command adds to your project configuration files required for using Ibexa Engage. ## Prepare configuration files In `config/packages` directory add the following `ibexa_connector_qualifio.yaml` [YAML configuration](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa_connector_qualifio: client_id: 1234 channel: 'd882c3f1-1b05-48c8-bc1d-c11a45e2c23a' feed_url: 'https://api.qualif.io/v1/campaignfeed/channels/d882c3f1-1b05-48c8-bc1d-c11a45e2c23a/json?clientId=1234' variable_map: form: shipping_address: first_name: 'firstname' last_name: 'lastname' email: 'email' street: 'address' postal_code: 'zip_code' locality: 'locality' country: 'country' phone_number: 'phone' billing_address: first_name: 'firstname' last_name: 'lastname' email: 'email' street: 'address' postal_code: 'zip_code' locality: 'locality' country: 'country' phone_number: 'phone' content: birthday: 'birthday' first_name: 'firstname' last_name: 'lastname' company: 'company' position: 'position' phone: 'phone' gender: 'gender' account: name: 'name' email: 'email' id: 'userid' remote_id: 'identifier' login: 'login' ``` - `client_id` - an identifier of the user. - `channel` - an UUID identifier format: a string of 30+ characters, divided by four hyphens, specific per publication channel. - `feed_url` - an URL link of the campaign feed. To create a campaign feed, follow the [Qualifio documentation](https://support.qualifio.com/hc/en-us/articles/360022954454-About-Campaign-Feeds). > **Note: Note** > > Ibexa configures the `channel` and `client_id` values so that the selections can be filled up automatically on Ibexa DXP side. > > The `feed_url` and `variable_map` values don't need to be set at the installation process. They're preconfigured and can be overwritten. # Create campaign Editions: Experience [Campaign](https://doc.ibexa.co/projects/userguide/en/5.0/ibexa_engage/ibexa_engage/#campaign) is a set of concepts, divided into steps, that the user can configure. It can contain, for example, a welcome screen, an interaction element, a form step, and an exit screen. To create new campaign, you need to use Qualifio Manager. You can use Qualifio's existing templates and interactive elements, such as quizzes, pools, and forms, to create visually appealing, customized campaigns. Users can configure the backgrounds, themes, or designs, and set up a specific time frame for each campaign. Technically, each campaign has a unique campaign ID, that is automatically defined by the Qualifio platform when it's created. For more information about creating and managing campaigns, see [Qualifio documentation](https://support.qualifio.com/hc/en-us/categories/202280638-Campaigns). ## Publication channels Each campaign includes a minimum of one publication channel that you can choose from the three options the platform provides for publishing a campaign. For more information about publication channels, see [Publication channel](https://doc.ibexa.co/projects/userguide/en/5.0/ibexa_engage/ibexa_engage/#publication-channel) in User Documentation. ## Use Campaign block in Page Builder You can add [Campaign block](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/block_reference/#campaign-block) in Page Builder to display campaign on the landing page. To select campaign, go to **Properties** tab. From the **Campaign** drop-down, choose campaign. This list includes all campaigns available on user's Qualifio account which are active or scheduled to launch in the future. You can set the dimensions of the field in which the campaign is displayed. To do it, insert width and height values in the proper fields. If size fields are blank, the system sets default template values. It's recommended to adjust them for better results. *[Image: Campaign block]* ## Embed campaign in the Rich text field You can embed campaign in the Rich text field with Campaign custom tag. To do it, insert **Campaign** content item in the Rich Text Field and choose campaign from the drop-down list. This list includes all campaigns available on user's Qualifio account which are active or scheduled to launch in the future. You can set the dimensions of the field in which the campaign is displayed. To do it, select units, and provide width and height values in the proper fields. If size fields are blank, the system sets default template values. It's recommended to adjust them for better results. *[Image: Campaign custom tag]* # Use Ibexa Connect Editions: Experience You can use [Ibexa Connect](https://doc.ibexa.co/projects/connect/en/latest/general/ibexa_connect/) to create workflows. Ibexa Engage collects user data and passes it directly to Ibexa Connect. With this data, you can create scenarios, for example, to add a user to newsletter, or to specific user segment group. For more information, see [Ibexa Connect documentation](https://doc.ibexa.co/projects/connect/en/latest/). ## Integrate Ibexa Engage with Ibexa Connect Webhooks provide a powerful way to transfer data between applications in real-time. You can use webhooks to connect Ibexa Engage with Ibexa Connect - integration platform (iPaaS). This integration allows to collect data using Ibexa Engage and then push it to another systems, such as CRMs, CDP, Marketing Automation platforms, or more. ### Get the webhook URL Use Ibexa Engage App and scenario to get the webhook URL from Ibexa Connect. To set up a webhook in Ibexa Connect, follow the steps: 1. Log in to your Ibexa Connect account. 1. Go to **Scenarios** and click the plus button to create a new scenario. 1. Select **Receive participation data**. *[Image: Create a scenario]* 4. Click **Create a webhook** and provide a name for the new webhook. 1. Click **Copy address to clipboard** to save the URL. *[Image: Create a webhook]* ### Configure Ibexa Engage The next step is to configure Ibexa Engage. When a form submission event takes place, data can be sent through the obtained webhook URL. To do it, perform the following actions:: 1. Log in to your Ibexa Engage account. 1. Go to **Engage** -> **Integrations** -> **Integrations** and select **Webhook**. 1. Paste the URL from the clipboard into **Webhook Host** field and click **Save**. *[Image: Configure Ibexa Engage]* 4. Then, go to **Engage** -> **Integrations** -> **Push rules** to define the default or specific rules for new campaign or website. Select the created webhook. # Multisite # Multisite A multisite setup enables you to create more than one site in one installation of Ibexa DXP. Multisite configuration is done using [SiteAccesses](https://doc.ibexa.co/en/latest/multisite/siteaccess/siteaccess/index.md). To quickly set up new sites with predefined site templates, use [Site Factory](https://doc.ibexa.co/en/latest/multisite/site_factory/site_factory/index.md). - [SiteAccess](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/multisite/siteaccess/siteaccess/): SiteAccesses enable you to provide separate configuration for each site in a multisite setup. - [Set up campaign SiteAccess](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/multisite/set_up_campaign_siteaccess/): Create a special SiteAccess to host a campaign site with different content subtree. - [Set up translation SiteAccess](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/multisite/set_up_translation_siteaccess/): Set up SiteAccesses to hold different language versions of a site. - [Multisite configuration](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/multisite/multisite_configuration/): Configure SiteAccesses to serve different content in different layouts. - [Site Factory](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/multisite/site_factory/site_factory/): Site Factory allows creating multiple sites (SiteAccesses) from the back office. - [Site Factory configuration](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/multisite/site_factory/site_factory_configuration/): Configure Site Factory, including site skeletons. # Multisite configuration You can configure the available SiteAccesses under the `ibexa.siteaccess` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files). ## SiteAccess configuration ``` ibexa: siteaccess: list: [site, event] groups: site_group: [site] event_group: [event] default_siteaccess: site match: URIElement: 1 ``` ### SiteAccess groups `ibexa.siteaccess.groups` defines which groups SiteAccesses belong to. ``` ibexa: siteaccess: groups: site_group: [site] event_group: [event] ``` You can use groups when you want to use common settings for several SiteAccesses and avoid duplicating configuration. SiteAccess groups act like regular SiteAccesses as far as configuration is concerned. A SiteAccess can be part of several groups. SiteAccess configuration has always precedence over group configuration. #### `admin` SiteAccess The predefined `admin` SiteAccess in `admin_group` (configured in `config/packages/ibexa_admin_ui.yaml`) serves the back office. Don't remove this group. If you need a multisite setup with multiple back offices, add any additional administration SiteAccesses to `admin_group`. In cases where the sites are on separate databases, each needs its own [repository](https://doc.ibexa.co/en/latest/administration/configuration/repository_configuration/index.md) (including their own storage and search connection), var dir, [cache pool](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/persistence_cache/#persistence-cache-configuration), and ideally also separate Varnish/Fastly configuration. > **Caution: Caution** > > Different SiteAccesses can only have different `var_dir` if they also have different repositories. Make sure there are no special or Unicode characters in your `var_dir` values. ### Default SiteAccess The `default_siteaccess` setting identifies which SiteAccess is used by default when no other SiteAccess matches. ``` ibexa: siteaccess: default_siteaccess: site ``` ### SiteAccess matching The `match` setting defines the rule or set of rules by which SiteAccesses are matched. For more information, see [SiteAccess matching](https://doc.ibexa.co/en/latest/multisite/siteaccess/siteaccess_matching/index.md). ``` ibexa: siteaccess: match: URIElement: 1 ``` ### SiteAccess name To create a better editorial experience, you can replace the SiteAccess code in the back office with a human-readable name of the website, for example `Company site` or `Summer Sale`. You can also translate SiteAccess names. Displayed names depend on the current back office language. To define translations or SiteAccess names, place them in YAML file with correct language code, for example `translations/ibexa_siteaccess.en.yaml`: ``` en: Company site fr: Company site France ``` ## Scope All SiteAccess-aware configuration is resolved depending on scope. The available scopes are: 1. `global` 2. SiteAccess 3. SiteAccess group 4. `default` `global` overrides all other scopes. If `global` isn't defined, the configuration then tries to match a SiteAccess, and then a SiteAccess group. Finally, if no other scope is matched, `default` is applied. In short: if you want a match that always applies, regardless of SiteAccesses, use `global`. To define a fallback, use `default`. ``` ibexa: system: global: # If set, this value is used regardless of any other configuration site: # This is used for the 'site' SiteAccess site_group: # This is overwritten by the SiteAccess above, since the SiteAccess has precedence default: # This value is only used if there is no setting for global scope, SiteAccess or SiteAccess group ``` `global` and `default` scopes include the `admin` SiteAccess, which is responsible for the back office. For example, the following configuration defines both the front template for articles and the template used in the back office, unless you configure other templates for a specific SiteAccess or SiteAccess group: ``` ibexa: system: default: content_view: full: article: template: full/article.html.twig match: Identifier\ContentType: [article] ``` ### SiteAccesses and Page Builder (Experience) (Commerce) To define which SiteAccesses are available in the submenu in Page Builder, use the following configuration: ``` ibexa: system: admin: page_builder: siteaccess_list: [site, de, fr, no] de: page_builder: siteaccess_list: [site, de] ``` If you're using multiple domains, list all domains for an admin SiteAccess under `siteaccess_hosts`: ``` ibexa: system: admin: page_builder: siteaccess_list: [site, de, fr, no] siteaccess_hosts: - my_domain.com - another_domain.org ``` > **Caution: SiteAccess with separate admin domain** > > If an admin SiteAccess in your installation uses a different domain than the front SiteAccesses, be sure to use SSL (https protocol). Otherwise, you cannot preview content in Page Builder from the back office. #### SiteAccess switching in Page Builder If you need to change between SiteAccesses in Site mode, don't use any functions in the page itself (for example, a language switcher). This may cause unexpected errors. Instead, switch between SiteAccesses with the SiteAccess bar above the page. ## Location tree You can restrict SiteAccesses to different parts of the content tree. When you do it, only the selected location and its descendants are reachable from this SiteAccess. Configure this under the `ibexa.systems..content.tree_root` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files), for example: ``` ibexa: system: : content: tree_root: location_id: 42 excluded_uri_prefixes: [/media/, /images/] index_page: /EventFrontPage ``` - `location_id` defines the location ID of the content root for the SiteAccess. - `excluded_uri_prefixes` defines which URIs ignore the root limit set by using `location_id`. In the example above, to access the Media and Images folders, you can use their own URI, even though they're outside the location provided in `content.tree_root.location_id`. It's an array of prefixes. So, for example, `[/media]` would also exclude `/mediation` from root limit. - `index_page` is the page shown when you access the root index `/`. > **Note: Note** > > Prefixes aren't case sensitive. Leading slashes (`/`) are automatically trimmed internally, so they can be ignored. > **Tip: Tip** > > For an example of a multisite configuration, see [Set up campaign SiteAccess](https://doc.ibexa.co/en/latest/multisite/set_up_campaign_siteaccess/index.md). # SiteAccess A SiteAccess is a set of configuration settings that the application uses when you access the site through a specific address. When the user visits the site, the system analyzes the URI and compares it to rules specified in the configuration. If it finds a set of fitting rules, this SiteAccess is used. Each SiteAccess can have different: - [templates and designs](https://doc.ibexa.co/en/latest/templating/design_engine/design_engine/index.md) - [languages](https://doc.ibexa.co/en/latest/multisite/set_up_translation_siteaccess/index.md) - [tree roots](https://doc.ibexa.co/en/latest/multisite/multisite_configuration/#location-tree) - [repositories](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/persistence_cache/#multi-repository-setup) - [recommendations](https://doc.ibexa.co/en/latest/personalization/enable_personalization/#configure-personalization) Many other settings in the application are also configured per SiteAccess (also known as "SiteAccess-aware"). > **Tip: Tip** > > When possible, always use semantic (SiteAccess-aware) configuration. Manually editing internal settings is possible, but at your own risk, as unexpected behavior can occur. - [SiteAccess matching](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/multisite/siteaccess/siteaccess_matching/): Use SiteAccess matchers to control which site is served when and to which user. - [SiteAccess-aware configuration](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/multisite/siteaccess/siteaccess_aware_configuration/): Make sure your custom development's configuration can be used with SiteAccesses. - [Injecting SiteAccess](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/multisite/siteaccess/injecting_siteaccess/): Inject the SiteAccess service to get SiteAccess information in your custom PHP code. # SiteAccess matching To be usable, every SiteAccess must be matched by one of configured matchers. By default, all SiteAccesses are matched using `URIElement: 1`. You can configure SiteAccess matchers under the `ibexa.siteaccess.match` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa: siteaccess: list: [site, event] groups: site_group: [site, event] default_siteaccess: site match: Map\URI: site: site campaign: event ``` `ibexa.siteaccess.match` can contain multiple matchers. The first matcher succeeding always wins, so be careful when using catch-all matchers like `URIElement`. In the following example, `Compound\LogicalAnd` is placed before the `Map\Host` for `my.site/corporate` to be reachable: ``` ibexa: siteaccess: match: Compound\LogicalAnd: corporate: matchers: Map\URI: corporate: true Map\Host: my.site: true match: corporate Map\Host: my.site: mysite ``` If the matcher class doesn't start with a backslash (`\`), it's relative to `Ibexa\Core\MVC\Symfony\SiteAccess\Matcher` (for example, `Map\URI` refers to `Ibexa\Core\MVC\Symfony\SiteAccess\Matcher\Map\URI`) You can specify [custom matchers](#custom-matchers) by using a fully qualified class name (for example, `\My\SiteAccess\Matcher`) or a service identifier (for example, `@my_matcher_service`). In the case of a fully qualified class name, the matching configuration is passed in the constructor. In the case of a service, it must implement `Ibexa\Bundle\Core\SiteAccess\Matcher`. The matching configuration is passed to `setMatchingConfiguration()`. ## Available SiteAccess matchers - [`URIElement`](#urielement) - [`URIText`](#uritext) - [`HostElement`](#hostelement) - [`HostText`](#hosttext) - [`Map\Host`](#maphost) - [`Map\URI`](#mapuri) - [`Map\Port`](#mapport) - [`Ibexa\SiteFactory\SiteAccessMatcher`](#ibexasitefactorysiteaccessmatcher) ### `URIElement` Maps a URI element to a SiteAccess. In configuration, provide the element number you want to match (starting from 1). ``` ibexa: siteaccess: match: URIElement: 2 ``` > **Note: Note** > > When you use a value > 1, the matcher concatenates the elements with `_`. Example URI `/my_site/company/pages` matches SiteAccess `my_site_company`. ### `URIText` Matches URI using prefix and suffix sub-strings in the first URI segment. In configuration, provide the prefix and/or suffix (neither is required). ``` ibexa: siteaccess: match: URIText: prefix: main- suffix: /company ``` Example URI `/main-event/company/page` matched SiteAccess `event`. ### `HostElement` Maps an element in the host name to a SiteAccess. In configuration, provide the element number you want to match (starting from 1). ``` ibexa: siteaccess: match: HostElement: 2 ``` Example host name `www.example.com` matches SiteAccess `example`. ### `HostText` Matches a SiteAccess in the host name, using pre and/or post sub-strings. In configuration, provide the prefix and/or suffix (none are required). ``` ibexa: siteaccess: match: HostText: prefix: www. suffix: .com ``` Example host name `www.example.com` matches SiteAccess `example`. ### `Map\Host` Maps a host name to a SiteAccess. In configuration, provide a hash map of host/SiteAccess. ``` ibexa: siteaccess: match: Map\Host: www.page.com: event adm.another-page.fr: event_admin ``` Example host name `www.page.com` matches SiteAccess `event`. > **Note: Note** > > If you encounter problems with the `Map\Host` matcher, make sure that your installation is [properly configured to use token-based authentication](https://doc.ibexa.co/en/latest/release_notes/ez_platform_v2.4/#update-ez-enterprise-v24-to-v242). ### `Map\URI` Maps a URI to a SiteAccess. In configuration, provide a hash map of URI/SiteAccess. ``` ibexa: siteaccess: match: Map\URI: campaign: event site: site ``` Example URI `/campaign/general/articles` matches SiteAccess `event`. ### `Map\Port` Maps a port to a SiteAccess. In configuration, provide a hash map of Port/SiteAccess. ``` ibexa: siteaccess: match: Map\Port: 80: event 8080: site ``` Example URL `http://my_site.com:8080/content` matches SiteAccess `site`. ### `Ibexa\SiteFactory\SiteAccessMatcher` (Experience) (Commerce) Enables the use of [Site Factory](https://doc.ibexa.co/en/latest/multisite/site_factory/site_factory/index.md). Doesn't take any parameters in configuration: ``` ibexa: siteaccess: match: '@Ibexa\SiteFactory\SiteAccessMatcher': ~ ``` ## Custom matchers Beside the built-in matchers, you can also use your own services to match SiteAcceses: ``` ibexa: siteaccess: list: [site] groups: site_group: [site] default_siteaccess: site match: '@App\Matcher\MySiteaccessMatcher': ~ ``` The service must be tagged with `ibexa.site_access.matcher` and must implement `Ibexa\Bundle\Core\SiteAccess\Matcher` (and `Ibexa\Core\MVC\Symfony\SiteAccess\VersatileMatcher` if you want to use compound logical matchers). ## Combining SiteAccess matchers You can combine more than one SiteAccess matcher to match more complex situations, for example: - `http://example.com/en` matches `site_en` (match host example.com and the `en` URI element) - `http://example.com/fr` matches `site_fr` (match host example.com and the `fr` URI element) - `http://admin.example.com` matches `site_admin` (match host admin.example.com) To combine matchers, use compound logical matchers: - `Compound\LogicalAnd` - `Compound\LogicalOr` Each compound matcher specifies two or more sub-matchers. A rule applies if all the matchers combined with the logical matcher are positive. To get the result above, you need to combine `Map\Host` and `Map\Uri` using `LogicalAnd`. When both the URI and host match, the SiteAccess configured with `match` is used. ``` ibexa: siteaccess: match: Compound\LogicalAnd: # You don't need to specify matching values (true is enough). site_en: matchers: Map\URI: en: true Map\Host: example.com: true match: site_en site_fr: matchers: Map\URI: fr: true Map\Host: example.com: true match: site_fr Map\Host: admin.example.com: site_admin ``` When using `Compound\LogicalAnd`, all inner matchers must match. All matchers must implement `VersatileMatcher`. When using `Compound\LogicalOr`, the first inner matcher succeeding wins. ## Matching by request header You can define which SiteAccess to use by setting an `X-Siteaccess` header in your request. This can be useful for REST requests. In such a case, `X-Siteaccess` must be the SiteAccess name (for example, `site` or `en`). ## Matching by environment variable You can also define which SiteAccess to use directly by using the `EZPUBLISH_SITEACCESS` environment variable. This is recommended if you want to get performance gain since no matching logic is done in this case. You can define this environment variable directly in web server configuration: ``` # This configuration assumes that mod_env is activated DocumentRoot "/path/to/ibexa/web/folder" ServerName example.com ServerAlias www.example.com SetEnv EZPUBLISH_SITEACCESS demo_site ``` > **Tip: Tip** > > You can configure the variable by using the PHP-FPM configuration file. For more information, see [PHP-FPM documentation](https://www.php.net/manual/en/install.fpm.configuration.php). > **Note: Precedence** > > The precedence order for SiteAccess matching is the following (the first matched wins): > > 1. Request header > 1. Environment variable > 1. Configured matchers # SiteAccess-aware configuration The [Symfony Config component](https://symfony.com/doc/7.4/components/config.html) makes it possible to define semantic configuration, exposed to the end developer. This configuration is validated by rules you define, for example, validating type (string, array, integer, boolean, and more). Usually, after it's validated and processed, this semantic configuration is then mapped to internal *key/value* parameters stored in the service container. Ibexa DXP uses this for its core configuration, but adds another configuration level, the SiteAccess. For each defined SiteAccess, you need to be able to use the same configuration tree to define SiteAccess-specific config. These settings then need to be mapped to SiteAccess-aware internal parameters that you can retrieve with the [ConfigResolver](https://doc.ibexa.co/en/latest/administration/configuration/dynamic_configuration/#configresolver). For this, internal keys need to follow the format `..`. where: - `namespace` is specific to your app or bundle - `scope` is the SiteAccess, SiteAccess group, `default` or `global` - `parameter_name` is the actual setting *identifier* For more information about the ConfigResolver, namespaces and scopes, see [configuration basics](https://doc.ibexa.co/en/latest/administration/configuration/configuration/index.md). > **Tip: Repository-aware configuration** > > If you need to use different settings per repository, not per SiteAccess, see [Repository-aware configuration](https://doc.ibexa.co/en/latest/administration/configuration/repository_configuration/#repository-aware-configuration). The example below assumes you're using an `Acme\ExampleBundle`. Remember to register the bundle by adding it to `config/bundles.php`: ``` Acme\ExampleBundle\AcmeExampleBundle::class => ['all' => true], ``` ### Parsing semantic configuration To parse semantic configuration, create a `Configuration` class which extends `Ibexa\Bundle\Core\DependencyInjection\Configuration\SiteAccessAware\Configuration` and then extend its `generateScopeBaseNode()` method: ``` The tree builder */ public function getConfigTreeBuilder(): TreeBuilder { $treeBuilder = new TreeBuilder('acme_example'); $rootNode = $treeBuilder->getRootNode(); // $systemNode is the root of SiteAccess-aware settings. $systemNode = $this->generateScopeBaseNode($rootNode); $systemNode ->scalarNode('name')->isRequired()->end() ->arrayNode('custom_setting') ->children() ->scalarNode('string')->end() ->integerNode('number')->end() ->booleanNode('enabled')->end() ->end() ->end(); return $treeBuilder; } } ``` > **Note: Note** > > Default name for the *SiteAccess root node* is `system`, but you can customize it. To do this, pass the name you want to use as a second argument of `$this->generateScopeBaseNode()`. This enables you to use the following SiteAccess-aware configuration: ``` acme_example: system: : name: name_1 custom_setting: number: 456 enabled: true : name: name_2 custom_setting: string: value number: 123 enabled: false ``` ### Mapping to internal settings Semantic configuration must always be mapped to internal key/value settings within the service container. You usually do it in the [service container](https://doc.ibexa.co/en/latest/api/php_api/php_api/#service-container) extension. ``` getConfiguration($configs, $container); $config = $this->processConfiguration($configuration, $configs); $loader = new Loader\YamlFileLoader($container, new FileLocator(self::ACME_CONFIG_DIR)); $loader->load('default_settings.yaml'); $processor = new ConfigurationProcessor($container, 'acme_example'); $processor->mapConfig( $config, // Any kind of callable can be used here. // It is called for each declared scope/SiteAccess. static function ($scopeSettings, $currentScope, ContextualizerInterface $contextualizer): void { // Maps the "name" setting to "acme_example.<$currentScope>.name" container parameter // It is then possible to retrieve this parameter through ConfigResolver in the application code: // $helloSetting = $configResolver->getParameter( 'name', 'acme_example' ); $contextualizer->setContextualParameter('name', $currentScope, $scopeSettings['name']); } ); // Now map "custom_setting" and ensure the key defined for "my_siteaccess" overrides the one for "my_siteaccess_group" // It is done outside the closure as it's needed only once. $processor->mapConfigArray('custom_setting', $config); } /** @param array $config */ #[\Override] public function getConfiguration(array $config, ContainerBuilder $container): Configuration { return new Configuration(); } } ``` You can also map simple settings by calling `$processor->mapSetting()`, without having to call `$processor->mapConfig()` with a callable. ``` $processor = new ConfigurationProcessor($container, 'acme_example'); $processor->mapSetting('name', $config); ``` > **Caution: Important** > > Always ensure you have defined and loaded default settings. In `@AcmeExampleBundle/Resources/config/default_settings.yaml`: ``` parameters: acme_example.default.name: name_1 acme_example.default.custom_setting: string: ~ number: 0 enabled: false ``` #### Merging hash values between scopes When you define a hash as semantic config, you sometimes don't want the SiteAccess settings to replace the default or group values, but enrich them by appending new entries. This is possible by using `$processor->mapConfigArray()`, which you must call outside the closure (before or after), so that it's called only once. ``` $processor->mapConfigArray('custom_setting', $config); ``` Consider the following default config in `default_settings.yaml`: ``` parameters: acme_example.default.custom_setting: string: ~ os_types: [windows] number: 0 enabled: false language: php ``` And then this semantic configuration in `config/packages/acme.yaml`: ``` acme_example: system: siteaccess_group: custom_setting: string: value number: 123 # Assuming "siteaccess1" is part of "siteaccess_group" siteaccess1: custom_setting: os_types: [linux, macos] number: 456 enabled: true language: javascript ``` By calling `mapConfigArray()` you can get the following end configuration, where keys defined for `custom_setting` in default/group/SiteAccess scopes are merged: ``` parameters: acme_example.siteaccess1.custom_setting: string: value os_types: [linux, macos] number: 456 enabled: true language: javascript ``` ##### Merging from second level In the example above, entries were merged in respect to the scope order of precedence. However, because you defined the `os_types` key for `siteaccess1`, it completely overrode the default value, because the merge process is done only at the first level. You can add another level by passing `ContextualizerInterface::MERGE_FROM_SECOND_LEVEL` as the third argument to `$contextualizer->mapConfigArray()`: ``` $contextualizer = $processor->getContextualizer(); $contextualizer->mapConfigArray('custom_setting', $config, ContextualizerInterface::MERGE_FROM_SECOND_LEVEL); ``` When you use `ContextualizerInterface::MERGE_FROM_SECOND_LEVEL` with the configuration above, you get the following result: ``` parameters: acme_example.siteaccess1.custom_setting: string: value os_types: [windows, linux, macos] number: 456 enabled: true language: javascript ``` There is also another option, `ContextualizerInterface::UNIQUE`, that ensures the array setting has unique values. It only works on normal arrays, not hashes. > **Note: Note** > > Merge isn't recursive. Only second level merge is possible by using `ContextualizerInterface::MERGE_FROM_SECOND_LEVEL` option. ### Dedicated mapper object Instead of passing a callable to `$processor->mapConfig()`, you can pass an instance of `Ibexa\Bundle\Core\DependencyInjection\Configuration\SiteAccessAware\ConfigurationMapperInterface`. This can be useful if you have a lot of configuration to map and don't want to pollute your service container extension class (it's better for maintenance). #### Merging hash values between scopes You should not use `$contextualizer->mapConfigArray()` within the scope loop, like for simple values. When using a closure/callable, you usually call it before or after `$processor->mapConfig()`. For mapper objects, you can use a dedicated interface: `HookableConfigurationMapperInterface`, which defines two methods: `preMap()` and `postMap()`. # Injecting SiteAccess The [service container](https://doc.ibexa.co/en/latest/api/php_api/php_api/#service-container) exposes the SiteAccess through the `Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessService` service, which fulfills the `Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface` contract. This means you can inject it into any custom service constructor, type hinting that contract. You can get the current SiteAccess from that service by calling the `SiteAccessServiceInterface::getCurrent` method. For example, define a service which depends on the Repository's ContentService and the SiteAccessService. ``` services: App\MyService: arguments: ['@Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessService'] ``` ``` declare(strict_types=1); namespace App; use Ibexa\Contracts\Core\Repository\ContentService; use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface; class MyService { /** @var \Ibexa\Contracts\Core\Repository\ContentService */ private $contentService; /** @var \Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface */ private $siteAccessService; public function __construct( SiteAccessServiceInterface $siteAccessService, ContentService $contentService ) { $this->siteAccessService = $siteAccessService; $this->contentService = $contentService; } } ``` # Set up campaign SiteAccess The following example shows how to set up a special `campaign` SiteAccess. This SiteAccess serves a site devoted to a special campaign, separate from the main company website (`site` SiteAccess). The `campaign` site uses a different part of the content tree than the main site, but shares some media files with it. ## Configure SiteAccesses First, in SiteAccess configuration, add the `campaign` SiteAccess to the list under the `ibexa.siteaccess` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa: siteaccess: list: [site, campaign] groups: site_group: [site, campaign] default_siteaccess: site match: Map\URI: summer-sale: campaign site: site ``` The `match` setting ensures that when a visitor accesses the `/summer-sale` URI, they see the `campaign` SiteAccess. ## Set root folder Next, with the following content structure, you need to separate the "Campaign" folder as root for the new site: *[Image: Content structure]* To do it, set the root level for `campaign` to access the "Campaign" Location and its sub-items only: ``` ibexa: system: campaign: content: tree_root: # LocationId of "Campaign" location_id: 57 ``` Thanks to this configuration, you can access `/campaign/Articles/Article2`, but not `/campaign/General/Articles/Article1`. ## Reuse content Finally, reuse some content between sites, for example "Logos" from "Images/Media". You can allow the `campaign` site to access them, even though they're in a different part of the tree, via [`excluded_uri_prefixes`](https://doc.ibexa.co/en/latest/multisite/multisite_configuration/#location-tree): ``` ibexa: system: campaign: content: tree_root: location_id: 57 excluded_uri_prefixes: [ /media/images/logos/ ] ``` Now, when you use the `campaign` SiteAccess, you can reach `/campaign/Media/Images/Logos`, despite the fact that it's not a sub-item of the "Campaign" location. As a next step, you can configure different [designs](https://doc.ibexa.co/en/latest/templating/design_engine/design_engine/index.md) for the two SiteAccesses. # Set up translation SiteAccess One of common uses for multisite installations is serving different language versions of a website. To do this, set up multiple SiteAccesses, each corresponding to one language. Proper configuration means avoiding duplicate content that could affect SEO. ## Add a language First, add a new language for the whole installation. > **Tip: Tip** > > For more details, see [Languages](https://doc.ibexa.co/en/latest/multisite/languages/languages/index.md). 1. In the back office, go to **Admin** -> **Languages**. 2. Click **Create a new language** and provide the language name and code (examples below use French with `fre-FR`). 3. After creating the new language, refresh the assets by running: ``` yarn encore ``` ## Configure SiteAccesses Next, configure a new SiteAccess to match the newly-configured language. The most typical setup for a site with translated content is to map the base of the domain to one language and use the first segment of the URI to match to translations. For example: - `www.mysite.com` for English site - `www.mysite.com/fr` for French site To achieve this you need to create a new SiteAccess in configuration under the `ibexa.siteaccesses` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files). Add the `fr` SiteAccess to list of all SiteAccesses and it to the common `site_group`. This group is used for sharing settings such as API keys, cache locations, and more. ``` ibexa: siteaccess: list: [site, fr] groups: site_group: [site, fr] ``` Under the `ibexa.system` key, add the new SiteAccess. Indicate that they're meant for translations under `site_group.translation_siteaccesses`: ``` ibexa: system: site_group: # ... translation_siteaccesses: [fr] fr: languages: [fre-FR, eng-GB] site: languages: [eng-GB] ``` With this configuration, the main English site displays content in English and ignores French content. The French site displays content in French, but also in English, if it doesn't exist in French. Clear the cache by running: `php bin/console cache:clear`. ## Set permissions By default, the Anonymous user role doesn't have permissions for new SiteAccesses. As a next step, allow Anonymous users to read content on the new SiteAccesses: 1. In the back office, go to **Admin** -> **Roles**. 2. Click the **Anonymous** role. 3. Edit the **Limitations** of the module `user`, select both SiteAccesses and click **Update**. 4. Clear the cache by running: `php bin/console cache:clear`. You can now start translating content. When you reload the site, access a translated content item through both SiteAccesses to see the difference, for example: `/` and `/fr/`. # Site Factory Editions: Experience Site Factory is a site management interface, integrated with the back office. It enables you to configure new sites without editing [YAML-based SiteAccess configuration](https://doc.ibexa.co/en/latest/multisite/multisite_configuration/index.md). > **Note: Note** > > A SiteAccess that you define for a site by following the [configuration](https://doc.ibexa.co/en/latest/multisite/multisite_configuration/index.md) is always treated with higher priority than a SiteAccess created by using the Site Factory. For example, if you define a French site within a YAML file, and then create a site that uses the `fr` path in Site Factory, matchers ignore the second site. Site Factory is disabled by default after installation. If you plan to use Site Factory, you need to enable and configure it. To enable or disable Site Factory, follow: - [Enable Site Factory section](#enable-site-factory) - [Disable Site Factory section](#disable-site-factory) ## Enable Site Factory To enable Site Factory, set the `ibexa_site_factory.enabled` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files) to `true`. ### Configure designs Next, configure Site Factory by adding empty SiteAccess groups. At least one empty group is required. The number of empty SiteAccess groups must be equal to the number of templates that you want to have when you create the new site. In this example, you add two SiteAccess groups (`example_site_factory_group_1` and `example_site_factory_group_2`) that correspond to the two templates (`site1` and `site2`) that you add in the next step. Add the groups under the `ibexa.siteaccess` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa: siteaccess: # ... groups: site_group: [import, site] storefront_group: [site] corporate_group: [corporate] example_site_factory_group_1: [ ] example_site_factory_group_2: [ ] system: example_site_factory_group_1: example_site_factory_group_2: ``` Uncomment the SiteAccess matcher (`Ibexa\SiteFactory\SiteAccessMatcher`): ``` ibexa: siteaccess: match: '@Ibexa\SiteFactory\SiteAccessMatcher': ~ ``` Next, add the [design engine](https://doc.ibexa.co/en/latest/templating/design_engine/design_engine/index.md) configuration for new specific designs and their theme lists: ``` ibexa_design_engine: design_list: example_1: [example_1_theme] example_2: [example_2_theme] ``` Finally, configure designs for empty SiteAccess groups: ``` ibexa: system: example_site_factory_group_1: design: example_1 example_site_factory_group_2: design: example_2 ``` ### Add site template configuration Add thumbnails and names for your site templates: ``` ibexa_site_factory: templates: site1: siteaccess_group: example_site_factory_group_1 name: Example site 1 thumbnail: /path/to/image/example-thumbnail_1.png site2: siteaccess_group: example_site_factory_group_2 name: Example site 2 thumbnail: /path/to/image/example-thumbnail_2.png ``` You can check the results of your work in the back office by going to **Site management** and selecting **Sites**. There, you should be able to add a new site and choose a design for it. ### Define domains To be able to see your site online, you need to define a domain for it. > **Caution: Define domain for production environment** > > These steps are for `dev` environment only. If you want to define domains in production environment, you need to configure Apache or Nginx by yourself. In the `.env` file change line 2 to: `COMPOSE_FILE=doc/docker/base-dev.yml:doc/docker/multihost.yml` Take a look into the `doc/docker/multihost.yml` file. Here you can define domains. To add a new domain, add it in `command:` and under frontend and backend aliases as shown in the example below: ``` services: web: command: /bin/bash -c "cd /var/www && cp -a doc/nginx/ibexa_params.d /etc/nginx && bin/vhost.sh --host-name=site.example.com --host-alias='admin.example.com test.example.com' --template-file=doc/nginx/vhost.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'" networks: frontend: aliases: - site.example.com - admin.example.com - test.example.com backend: aliases: - site.example.com - admin.example.com - test.example.com ``` Next, you must define the domains in `etc/hosts`: `0.0.0.0 site.example.com admin.example.com test.example.com www.admin.example.com` Then, run `docker-compose up`: ``` export COMPOSE_FILE="doc/docker/base-dev.yml:doc/docker/multihost.yml" docker-compose up ``` Your sites should be now visible under: - `http://site.example.com:8080/` - `http://admin.example.com:8080/` - `http://localhost:8080/` - `http://test.example.com:8080/` *[Image: Site Factory enabled]* ### Define site directory You can adjust the place where the directory of the new site is created (location with ID 2 by default). To do it, go to configuration files and under the `ibexa.system..site_factory` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files) add the following parameter: ``` ibexa: system: default: site_factory: sites_location_id: 42 ``` Now, all new directories are created under "Ibexa DXP". ### Provide access The Site Factory is set up, now you can provide sufficient permissions to the users. Set the below policies to allow users to: - `site/view` - enter the Site Factory interface - `site/create` - create sites - `site/edit` - edit sites - `site/change_status` - change status of the public accesses to `Live` or `Offline` - `site/delete` - delete sites For full documentation on how permissions work and how to set them up, see [the permissions section](https://doc.ibexa.co/en/latest/permissions/permissions/index.md). To learn how to use Site Factory, see [User Documentation](https://doc.ibexa.co/projects/userguide/en/5.0/website_organization/work_with_sites/). ## Disable Site Factory Enabled Site Factory may cause following performance issues: - [ConfigResolver](https://doc.ibexa.co/en/latest/administration/configuration/dynamic_configuration/#configresolver) looks for SiteAccesses in the database - Site Factory matchers are connected to the database in search for new SiteAccesses You can disable Site Factory to boost ConfigResolver performance. Keep in mind that with disabled Site Factory you're unable to add new sites or use existing ones. 1. In `config/packages/ibexa_site_factory.yaml` change `enabled` to `false`. 1. In `config/packages/ibexa.yaml` comment the `ibexa.siteaccess.match: '@Ibexa\SiteFactory\SiteAccessMatcher': ~` if it's uncommented. 1. Remove separate connection to database in `config/packages/doctrine.yaml`. ``` doctrine: dbal: connections: ... # This connection is dedicated for SiteFactory to avoid known issues site_factory: ``` 4. Remove separate cache pool in `config/packages/cache.yaml`. ``` framework: cache: ... pools: # This pool should be used only by SiteFactory bundle site_factory_pool: ``` The Site Factory should be disabled. # Site Factory configuration Editions: Experience ## Parent location When working with the [Site Factory](https://doc.ibexa.co/en/latest/multisite/site_factory/site_factory/index.md), you can define the parent location for a new site in the configuration. Each new site is created in the designated location. To define a parent location, add a new configuration key to the site template definition. Each template is assigned to its own location. This can be either a location ID (for example, `62`), or a recommended remote location ID (for example, `1548b8cd8dd4c6b5082e566615d45e91`). Add the configuration key to your template under the `ibexa_site_factory` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa_site_factory: templates: site1: siteaccess_group: example_site_factory_group_1 name: example_site_1 thumbnail: /path/to/image/example-thumbnail_1.png parent_location_id: 62 site2: siteaccess_group: example_site_factory_group_2 name: example_site_2 thumbnail: /path/to/image/example-thumbnail_2.png parent_location_remote_id: 1548b8cd8dd4c6b5082e566615d45e91 ``` Now, you can see the path to the new site's parent location under design selection. If you have sufficient permissions, you can change the defined location during site creation. If the parent location isn't defined, you have to choose it from Universal Discovery Widget. ## Site skeletons The Site skeleton enables you to copy an entire content structure of the site design to the defined location. Site skeleton copying is a one-off operation, it only happens during the site creation process. After that, you cannot copy the Site skeleton again, for example in the edit view. You can create as many skeletons as you need and assign them to templates. Remember that one template can only have one Site skeleton. If the design doesn't have a defined Site skeleton, a directory of the new site is created in a standard Site Factory process. To define a Site skeleton, add the `site_skeleton_id` or `site_skeleton_remote_id` key to the site template definition. This can be either a location ID (for example, `5966`), or a remote location ID (for example, `3bed95afb1f8126f06a3c464e461e1ae66`). ``` ibexa_site_factory: templates: site1: siteaccess_group: example_site_factory_group_1 name: example_site_1 thumbnail: /path/to/image/example-thumbnail_1.png site_skeleton_id: 5966 site2: siteaccess_group: example_site_factory_group_2 name: example_site_2 thumbnail: /path/to/image/example-thumbnail_2.png site_skeleton_remote_id: 3bed95afb1f8126f06a3c464e461e1ae66 ``` Now, you can choose a design with a defined Site skeleton, and decide if you want to use its skeleton by toggling **Generate site using site skeleton**. ## User group skeletons With user group skeletons you can define policies and limitations that apply to selected groups of users who can access the site. You can create many user group skeletons and associate them with many templates. One template can have many user group skeletons assigned. To create a user group skeleton, first go to **Admin** -> **Site skeletons** and add a user group to the list of available skeletons. Then, review the detailed information of the newly created user group skeleton, copy the location ID or the Location remote ID, and add a configuration key to the site template definition: ``` ibexa_site_factory: templates: : # ... user_group_skeleton_ids: [ , , ... ] user_group_skeleton_remote_ids: [ , , ... ] ``` Manage the permissions associated to the user group skeleton by [assigning roles](https://doc.ibexa.co/projects/userguide/en/5.0/permission_management/work_with_permissions/#assign-a-role-to-users). Make sure that the roles that you assign to the user group skeleton don't contain location-based limitations. User group skeletons cannot contain individual user content items either. User group skeletons are retained after deleting the site. ## Automatic update of roles Role definitions can contain user/login policies with limitations that limit user access to certain sites. To avoid the need to add the new SiteAccess to limitations for all roles, you can decide that the roles you select are automatically updated when the site is created, updated, or deleted. Under the `ibexa_site_factory` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files), add a list of roles which should have access to the frontend when a site is created in Site Factory, for example: ``` ibexa_site_factory: # ... enabled: true update_roles: [Anonymous, Administrator] ``` For more information about roles and policies, see [Permissions](https://doc.ibexa.co/en/latest/permissions/permissions/index.md). # Languages ## Language versions Ibexa DXP offers the ability to create multiple language versions (translations) of a content item. Translations are created per version of the item, so each version of the content can have a different set of translations. A version always has at least one translation which by default is the *initial/main* translation. Further versions can be added, but only for languages that have previously been [added to the global translation list](#adding-available-languages), that is a list of all languages available in the system. The maximum number of languages in the system is 62. Different translations of the same content item can be edited separately. This means that different users can work on translations into different languages at the same time. Each version, including a draft, contains all the existing translations. However, even if work on a draft takes time and other translations are updated in the meantime, publishing the draft doesn't overwrite later modifications. ### Adding available languages The multilanguage system operates based on a global translation list that contains all languages available in the installation. Languages can be [added to this list from the **Admin** panel](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/translate_content/) in the back office. After adding a language be sure to dump all assets to the file system: ``` yarn encore # OR php bin/console ibexa:encore:compile ``` **The new language must then be added to the [SiteAccess](https://doc.ibexa.co/en/latest/multisite/multisite/index.md) configuration**. Once this is done, any user with proper permissions can create content item versions in these languages in the user interface. ### Translatable and untranslatable fields Language versions consist of translated values of the content item's fields. In the content type definition every field is set to be Translatable or not. Ibexa DXP doesn't decide by itself which fields can be translated and which cannot. For some field values the need for a translation can be obvious, for example for the body of an article. In other cases, for instance images without text, integer numbers, or email addresses, translation is usually unnecessary. Despite that, Ibexa DXP gives you the possibility to mark any field as translatable regardless of its field type. It's only your decision to exclude the translation possibility for those fields where it makes no sense. When a field isn't flagged as Translatable, its value is copied from the initial/main translation when a new language version is created. This copied value cannot be modified. When a field is Translatable, you have to enter its value in a new language version manually. For example, let's say that you need to store information about marathon contestants and their results. You build a "contestant" content type that includes the following fields: name, photo, age, nationality, finish time. Allowing the translation of anything other than nationality would be pointless, since the values stored by the other fields are the same regardless of the language used to describe the contestant. In other words, the name, photo, age and finish time would be the same in, for example, both English and Norwegian. ### Access control You can control whether a user or user group is able to translate content or not. You do this by adding a [Language limitation](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#language-limitation) to policies that allow creating or editing content. This limitation enables you to define which role can work with which languages in the system. For more information of the permissions system, see [Permissions](https://doc.ibexa.co/en/latest/permissions/permissions/index.md). In addition, you can also control the access to the global translation list by using the `Content/Translations` policy. This policy allows users to add and remove languages from the global translation list. ## Using SiteAccesses for handling translations If you want to have completely separate versions of the website, each with content in its own language, you can [use SiteAccesses](#using-siteaccesses-for-handling-translations). Depending on the URI used to access the website, a different site opens, with a language set in configuration settings. All content items are then displayed in this language. For details, see [Multi-language SiteAccesses](https://doc.ibexa.co/en/latest/multisite/set_up_translation_siteaccess/index.md). ### Explicit translation SiteAccesses Configuration isn't mandatory, but can help to distinguish which SiteAccesses can be considered translation SiteAccesses. ``` ibexa: siteaccess: default_siteaccess: eng list: - site - eng - fre - site_admin groups: frontend_group: - site - eng - fre # ... system: # Specifying which SiteAccesses are used for translation frontend_group: translation_siteaccesses: [fre, eng] eng: languages: [eng-GB] fre: languages: [fre-FR, eng-GB] site: languages: [eng-GB] ``` > **Note: Note** > > The top prioritized language is always used the SiteAccess language reference (for example, `fre-FR` for `fre` SiteAccess in the example above). If several translation SiteAccesses share the same language reference, **the first declared SiteAccess always applies**. #### Custom locale configuration If you need to use a custom locale, you can configure it in `ibexa.yaml`, adding it to the *conversion map*: ``` ibexa: # Locale conversion map between eZ Publish format (e.g. fre-FR) to POSIX (e.g. fr_FR). # The key is the eZ Publish locale. Check locale.yaml in IbexaCore to see natively supported locales. locale_conversion: eng-DE: en_DE ``` A locale *conversion map* example [can be found in `ibexa/core`, in `locale.yaml`](https://github.com/ibexa/core/blob/main/src/bundle/Core/Resources/config/locale.yml). ### More complex translation setup There are some cases where your SiteAccesses share settings (for example, repository or content settings), but you don't want all of them to share the same `translation_siteaccesses` setting. This can be for example the case when you use separate SiteAccesses for mobile versions of a website. The solution is defining new groups: ``` ibexa: siteaccess: default_siteaccess: eng list: - site - eng - fre - mobile_eng - mobile_fre - site_admin groups: # This group can be used for common front settings common_group: - site - eng - fre - mobile_eng - mobile_fre frontend_group: - site - eng - fre mobile_group: - mobile_eng - mobile_fre # ... system: # Translation SiteAccesses for regular frontend frontend_group: translation_siteaccesses: [fre, eng] # Translation SiteAccesses for mobile frontend mobile_group: translation_siteaccesses: [mobile_fre, mobile_eng] eng: languages: [eng-GB] fre: languages: [fre-FR, eng-GB] site: languages: [eng-GB] mobile_eng: languages: [eng-GB] mobile_fre: languages: [fre-FR, eng-GB] ``` ### Using implicit *related SiteAccesses* If the `translation_siteaccesses` setting isn't provided, implicit *related SiteAccesses* is used instead. SiteAccesses are considered *related* if they share: - The same repository - The same root `location_id` (see [Multisite](https://doc.ibexa.co/en/latest/multisite/multisite/index.md)) ### Fallback languages and missing translations When setting up SiteAccesses with different language versions, you can specify a list of preset languages for each SiteAccess. When this SiteAccess is used, the system goes through this list. If a content item is unavailable in the first (prioritized) language, it attempts to use the next language in the list, and more. Thanks to this you can have a fallback in case of a lacking translation. You can also assign a Default content availability flag to content types (available in the **Admin** panel). When this flag is assigned, content items of this type are available even when they don't have a language version in any of the languages configured for the current SiteAccess. If a language isn't provided in the list of prioritized languages and it's not the content item's first language, the URL alias for this content in this language isn't generated. # Language API You can manage languages configured in the system with PHP API by using [`LanguageService`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-LanguageService.html). ## Getting language information To get a list of all languages in the system use [`LanguageService::loadLanguages`:](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-LanguageService.html#method_loadLanguage) ``` $languageList = $this->languageService->loadLanguages(); foreach ($languageList as $language) { $output->writeln($language->languageCode . ': ' . $language->name); } ``` ## Creating a language To create a new language, you need to create a [`LanguageCreateStruct`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-LanguageCreateStruct.html) and provide it with the language code and language name. Then, use [`LanguageService::createLanguage`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-LanguageService.html#method_createLanguage) and pass the `LanguageCreateStruct` to it: ``` $languageCreateStruct = $this->languageService->newLanguageCreateStruct(); $languageCreateStruct->languageCode = 'pol-PL'; $languageCreateStruct->name = 'Polish'; $this->languageService->createLanguage($languageCreateStruct); $output->writeln('Added language Polish with language code pol-PL.'); ``` # Back office translations ## Enabling back office languages All translations are available as a part of Ibexa DXP. To enable back office translations, use the following configuration: ``` ibexa: ui: translations: enabled: true ``` Then clear the cache. Now you can reload your Ibexa DXP back office. If your browser language is set to French, the back office is displayed in French. > **Tip: Checking browser language** > > To make sure that a language is set in your browser, check if it's sent as an accepted language in the `Accept-Language` header. > **Tip: Tip** > > You can also manually add the necessary .xliff files to an existing project. > > Add the language to an array under `ibexa.system..user_preferences.additional_translations`, for example: > > `ibexa.system..user_preferences.additional_translations: ['pl_PL', 'fr_FR']` > > Then, run `composer run post-update-cmd` and `php bin/console cache:clear --siteaccess=admin`. ### Contributing back office translations To learn how to contribute to a translation, see [Contributing translations](https://doc.ibexa.co/en/latest/resources/contributing/contribute_translations/index.md). ### Selecting back office language Once you have language packages enabled, you can switch the language of the back office in the **User Settings** menu. Otherwise, the language is selected based on the browser language. If you don't have a language defined in the browser, the language is selected based on `parameters.locale_fallback` in `config/packages/ibexa.yaml`. ## Custom string translations When you extend the back office you often need to provide labels for new elements. It's good practice to provide your labels in translations files, instead of literally, so they can be reused and translated into other languages. To provide label strings, make use of the `Symfony\Component\Translation\TranslatorInterface` and its `trans()` method. The method takes as arguments: - `id` of the message you want to translate - an array of parameters - domain of the string Here's an example: ``` use Symfony\Component\Translation\TranslatorInterface; private $translator; public function __construct(TranslatorInterface $translator) { $this->translator = $translator; } private function getTranslatedDescription(): string { return $this->translator->trans( 'custom.extension.description', [], 'custom_extension' ); } ``` The strings are provided in .xliff files. The file should be stored in your project's or your bundle's `Resources/translations` folder. File name corresponds to the selected domain and the language, for example, `custom_extension.en.xliff`. ```
    The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message.
    My custom label My custom label key: custom.extension.description
    ``` To provide a translation into another language, add it in the `` tag. For example, in `custom_extension.de.xliff`: ``` My custom label Meine benutzerdefinierte Bezeichnung key: custom.extension.description ``` The language to display is then selected automatically based on [user preferences or browser setup](#selecting-back-office-language). > **Note: Note** > > Run `composer run post-update-cmd` which installs your JavaScript translations by using `BazingaJsTranslationBundle`, and clears the cache of the default SiteAccess. > > Run `php bin/console cache:clear --siteaccess=admin` to clear the back office cache. You may need to replace `admin` with the back office's SiteAccess name used in your installation. # Automated content translation With the automated translation add-on package, users can translate their content items into multiple languages automatically by using either Google Translate or DeepL external translation engine. The package integrates with Ibexa DXP, and allows users to [request from the UI](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/translate_content/#add-translations) that a content item is translated. However, you can also run a Console Command to translate a specific content item. Either way, as a result, a new version of the content item is created. The following field types are supported out of the box: - [TextLine](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/textlinefield/index.md) - [TextBlock](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/textblockfield/index.md) - [RichText](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/richtextfield/index.md) - [Page](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/pagefield/index.md): the content of `text` and `richtext` [block attributes](https://doc.ibexa.co/en/latest/content_management/pages/page_block_attributes/#block-attribute-types) See [adding a custom field or block attribute encoder](#create-custom-field-or-block-attribute-encoder) for more information on how you can extend this list. ## Configure automated content translation ### Install package The automated content translation feature comes as an additional package that you must download and install separately: ``` composer require ibexa/automated-translation ``` > **Caution: Modify the default configuration** > > Symfony Flex installs and activates the package. However, you must modify the `config/bundles.php` file to change the bundle loading order so that `IbexaAutomatedTranslationBundle` is loaded before `IbexaAdminUiBundle`: > > ``` > > return [ > // ... > Ibexa\Bundle\AutomatedTranslation\IbexaAutomatedTranslationBundle::class => ['all' => true], > Ibexa\Bundle\AdminUi\IbexaAdminUiBundle::class => ['all' => true], > // ... > ]; > ``` ### Configure access to translation services Before you can start using the feature, you must configure access to your Google and/or DeepL account. 1. Get the [Google API key](https://developers.google.com/maps/documentation/javascript/get-api-key) and/or [DeepL Pro key](https://support.deepl.com/hc/en-us/articles/360020695820-API-Key-for-DeepL-s-API). 1. Set these values in the YAML configuration files, under the `ibexa_automated_translation.system.default.configurations` key: ``` ibexa_automated_translation: system: default: configurations: google: apiKey: "google-api-key" deepl: authKey: "deepl-pro-key" ``` The configuration is SiteAccess-aware, therefore, you can configure different engines to be used for different sites. ## Translate content items with CLI To create a machine translation of a specific content item, you can use the `ibexa:automated:translate` command. The following arguments and options are supported: - `--from` - the source language - `--to` - the target language - `contentId` - ID of the content to translate - `serviceName` - the service to use for translation For example, to translate the root content item from English to French with the help of Google Translate, run: ``` php bin/console ibexa:automated:translate --from=eng-GB --to=fre-FR 52 google ``` ## Extend automated content translations ### Add a custom machine translation service By default, the automated translation package can connect to Google Translate or DeepL, but you can configure it to use a custom machine translation service. You would do it, for example, when a new service emerges on the market, or your company requires that a specific service is used. The following example adds a new translation service. It uses the [AI actions framework](https://doc.ibexa.co/en/latest/ai_actions/ai_actions/index.md) and assumes a custom `TranslateAction` AI Action exists. To learn how to build custom AI actions see [Extending AI actions](https://doc.ibexa.co/en/latest/ai_actions/extend_ai_actions/#custom-action-type-use-case). 1. Create a service that implements the [`\Ibexa\AutomatedTranslation\Client\ClientInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-AutomatedTranslation-Client-ClientInterface.html) interface: ``` */ private array $supportedLanguages; public function __construct( private readonly ActionServiceInterface $actionService, private readonly ActionConfigurationServiceInterface $actionConfigurationService ) { } public function setConfiguration(array $configuration): void { if (!array_key_exists('languages', $configuration)) { throw new ClientNotConfiguredException('List of supported languages is missing in the configuration under the "languages" key'); } $this->supportedLanguages = $configuration['languages']; } public function translate(string $payload, ?string $from, string $to): string { $action = new TranslateAction(new Text([$payload])); $action->setRuntimeContext( new RuntimeContext( [ 'from' => $from, 'to' => $to, ] ) ); $actionConfiguration = $this->actionConfigurationService->getActionConfiguration('translate'); $actionResponse = $this->actionService->execute($action, $actionConfiguration)->getOutput(); assert($actionResponse instanceof Text); return $actionResponse->getText(); } public function supportsLanguage(string $languageCode): bool { return in_array($languageCode, $this->supportedLanguages, true); } public function getServiceAlias(): string { return 'aiclient'; } public function getServiceFullName(): string { return 'Custom AI Automated Translation'; } } ``` 2. Tag the service as `ibexa.automated_translation.client` in the Symfony container: ``` App\AutomatedTranslation\AiClient: tags: - ibexa.automated_translation.client ``` 3. Specify the configuration under the `ibexa_automated_translation.system.default.configurations` key: ``` ibexa_automated_translation: system: default: configurations: aiclient: languages: - 'en_GB' - 'fr_FR' ``` ### Create custom field or block attribute encoder You can expand the list of supported field types and block attributes for automated translation, adding support for even more use cases than the ones built into Ibexa DXP. The whole automated translation process consists of 3 phases: 1. **Encoding** - data is extracted from the field types and block attributes and serialized into XML format 2. **Translating** - the serialized XML is sent into specified translation service 3. **Decoding** - the translated response is deserialized into the original data structures for storage in Ibexa DXP The following example adds support for automatically translating alternative text in image fields. 1. Create a class implementing the [`FieldEncoderInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-AutomatedTranslation-Encoder-Field-FieldEncoderInterface.html) and add the required methods: ``` fieldTypeIdentifier === 'ibexa_image'; } public function canDecode(string $type): bool { return $type === 'ibexa_image'; } public function encode(Field $field): string { /** @var \Ibexa\Core\FieldType\Image\Value $value */ $value = $field->getValue(); return $value->alternativeText ?? ''; } /** * @param string $value * @param \Ibexa\Core\FieldType\Image\Value $previousFieldValue */ public function decode(string $value, $previousFieldValue): Value { $previousFieldValue->alternativeText = $value; return $previousFieldValue; } } ``` In this example, the methods are responsible for: - `canEncode` - deciding whether the field to be encoded is an [Image](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/imagefield/index.md) field - `canDecode` - deciding whether the field to be decoded is an [Image](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/imagefield/index.md) field - `encode` - extracting the alternative text from the field type - `decode` - saving the translated alternative text in the field type's value object 2. Register the class as a service. If you're not using [Symfony's autoconfiguration](https://symfony.com/doc/7.4/service_container.html#the-autoconfigure-option), use the `ibexa.automated_translation.field_encoder` service tag. ``` App\AutomatedTranslation\ImageFieldEncoder: tags: - ibexa.automated_translation.field_encoder ``` For custom block attributes, the appropriate interface is [`BlockAttributeEncoderInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-AutomatedTranslation-Encoder-BlockAttribute-BlockAttributeEncoderInterface.html) and the service tag is `ibexa.automated_translation.block_attribute_encoder`. # Permissions # Permissions The permission system of Ibexa DXP enables you to control in detail which users have access to which parts of the system, both the back office's administrative and editorial features, and the content of the website front. - [Permission overview](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/permissions/permission_overview/): The permission system is based on policies that you assign to users or user groups in the form of roles. - [Policies](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/permissions/policies/): Policies are the main building block of the permissions system which lets you define the accesses for specific user roles. - [Permission use cases](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/permissions/permission_use_cases/): Set up permission sets for common use cases. - [Limitations](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/permissions/limitations/): Control access to parts of the system by fine-tuning permissions with the use of Limitations. # Permission overview A new user doesn't have permissions for any part of the system, unless they're explicitly given access. To get access they need to inherit roles, typically assigned to the user group they belong to. Each role can contain one or more **Policies**. A policy is a rule that gives access to a single **function** in a **module**. For example, a `section/assign` policy allows the user to assign content to sections. When you add a policy to a role, you can also restrict it using one or more **Limitations**. A policy with a limitation only applies when the condition in the limitation is fulfilled. For example, a `content/publish` policy with a `ContentType` limitation on the "Blog Post" content type allows the user to publish only Blog Posts, and not other content. A limitation, like a policy, specifies what a user *can* do, not what they *can't do*. A `Section` limitation, for example, *gives* the user access to the selected section, not *prohibits* it. For more information, see [Limitation reference](https://doc.ibexa.co/en/latest/permissions/limitation_reference/index.md) and [Permission use cases](https://doc.ibexa.co/en/latest/permissions/permission_use_cases/index.md). ## Assigning roles to users Every user or user group can have many roles. A user can also belong to many groups, for example, Administrators, Editors, Subscribers. It's best practice to avoid assigning roles to users directly. Instead, try to organize your content so that it can be covered with general roles assigned to user groups. Using groups is easier to manage and more secure. It also improves system performance. The more role assignments and complex policies you add for a given user, the more complex the search/load queries are, because they always take permissions into account. ## Permissions for custom controllers You can control access to a custom controller by implementing the `performAccessCheck()` method. In the following example the user doesn't have access to the controller unless they have the `section/view` policy: ``` use Ibexa\Core\MVC\Symfony\Security\Authorization\Attribute; public function performAccessCheck(): void { parent::performAccessCheck(); $this->denyAccessUnlessGranted(new Attribute('section', 'view')); } ``` `Attribute` accepts three arguments: - `module` is the policy module (for example,`content`) - `function` is the function inside the module (for example, `read`) - `limitations` are optional limitations to check against. Here you can provide two keys: - `valueObject` is the object you want to check for, for example `ContentInfo`. - `targets` are a table of value objects that are the target of the operation. For example, to check if content can be assigned to a Section, provide the Section as `targets`. `targets` accept location, object state and section objects. ### Checking user access To check if a user has access to an operation, use the `isGranted()` method. For example, to check if content can be assigned to a Section: ``` $hasAccess = $this->isGranted( new Attribute('section', 'assign', ['valueObject' => $contentInfo, 'targets' => [$section]]) ); ``` You can also use the permission resolver (`Ibexa\Core\Repository\Permission\PermissionResolver`). The `canUser()` method checks if the user can perform a given action with the selected object. For example: `canUser('content', 'edit', $content, [$location] );` checks the `content/edit` permission for the provided content item at the provided location. ### Blocking access to controller action To block access to a specific action of the controller, add the following to the action's definition: ``` $this->denyAccessUnlessGranted(new Attribute('state', 'administrate')); ``` # Permission use cases Here are a few examples of sets of policies that you can use to get some common permission configurations. ## Enter back office To allow the user to enter the back office interface and view all content, set the following policies: - `user/login` - `content/read` - `content/versionread` - `section/view` - `content/reverserelatedlist` These policies are necessary for all other cases below that require access to the content structure. ## Create content without publishing (Experience) (Commerce) You can use this option together with Ibexa Experience's content review options. Users assigned with these policies can create content, but cannot publish it. To publish, they must send the content for review to another User with proper permissions (for example, senior editor or proofreader). - `content/create` - `content/edit` Use this setup with Ibexa Experience or Ibexa Commerce only, as Ibexa Headless doesn't allow the User to continue working with their content. ## Create and publish content To create and publish content, users must additionally have the following policies: - `content/create` - `content/edit` - `content/publish` This also lets the user copy and move content, and add new locations to a content item (but not remove them). ## Move content To move a content item or a subtree to another location, the user must have the following policies: - `content/read` - on the source location - `content/create` - on the target location ## Remove content To send content to Trash, the user needs to have the `content/remove` policy. If content has more than one language, the user must have access to all the languages. That is, the `content/remove` policy must have either no limitation, or a limitation for all languages of the content item. To remove an archived version of content, the user must have the `content/versionremove` policy. Further manipulation of Trash requires the `content/restore` policy to restore items from Trash, and `content/cleantrash` to completely delete all content from the Trash. > **Caution: Caution** > > With the `content/cleantrash` policy, the user can empty the Trash even if they don't have access to the trashed content, for example, because it belonged to a Section that the user doesn't have permissions for. ## Restrict editing to part of the tree If you want to let the User create or edit content, but only in one part of the content tree, use limitations. Three limitations that you could use here are `Section` limitation, `Location` limitation and `Subtree of Location` limitation. ### Section limitation Let's assume you have two Folders under your Home: Blog and Articles. You can let a user create content for the blogs, but not in Articles, by adding a `Section` limitation to the Blog content item. This allows the User to publish content anywhere under this location in the structure. Section doesn't have to belong to the same subtree of location in the content structure, any locations can be assigned to it. ### Location limitation If you add a `Location` limitation and point to the same location, the user is able to publish content directly under the selected location, but not anywhere deeper in its subtree of location. ### Subtree of location limitation To limit the user's access to a subtree, use the `Subtree of Location` limitation. You do it by creating two new roles for a user group: 1. Role with a `Subtree` limitation for the User 2. Role with a `Location` limitation for the subtree Follow the example below to learn how to do that. **Cookbook**, **Dinner recipes** and **Dessert recipes** containers aren't accessible in the frontend. Edit access to them in the **Admin** panel. *[Image: Subtree file structure]* To give the vegetarian editors access only to the **Vegetarian** dinner recipes section, create a new role, for example, *EditorVeg*. Next, add to it a `content/read` policy with the `Subtree` limitation for `Cookbook/Dinner recipes/Vegetarian`. Assign the role to the vegetarian editors user group. It allows users from that group to access the **Vegetarian** container but not **Cookbook** and **Dinner recipes**. To give users access to **Cookbook** and **Dinner recipes** containers, create a new role, for example, *EditorVegAccess*. Next, add to it a `content/read` policy with the `Location` limitations **Cookbook** and **Dinner recipes**. Assign the new role to the vegetarian editors user group as well. Only then the limitations are combined with `AND`, resulting in an empty set. The vegetarian editors should now see the following content tree: *[Image: Limited subtree file structure]* When a policy has more than one limitation, all of them have to apply, or the policy doesn't work. For example, a `Location` limitation on location `1/2` and `Subtree of Location` limitation on `1/2/55` cannot work together, because no location can satisfy both those requirements at the same time. To combine more than one limitation with the *or* relation, not *and*, you can split your policy in two, each with one of these limitations. ## Manage locations To add a new location to a content item, the policies required for publishing content are enough. To allow the user to remove a location, grant them the following policies: - `content/remove` - `content/manage_locations` Hiding and revealing location requires one more policy: `content/hide`. ## Editorial workflows You can control which stages in an editorial workflow the user can work with. Do this by adding the `WorkflowStageLimitation` to `content` policies such as `content/edit` or `content/publish`. You can also control which transitions the user can pass content through. Do this by using the `workflow/change_stage` policy together with the `WorkflowTransitionLimitation`. For example, to enable the user to edit only content in the "Design" stage and to pass it after creating design to the "Proofread stage", use following permissions: - `content/edit` with `WorkflowStageLimitation` set to "Design". - `workflow/change_stage` with `WorkflowTransitionLimitation` set to `to_proofreading` When using the [Collaborative editing feature](https://doc.ibexa.co/en/latest/content_management/collaborative_editing/collaborative_editing/index.md), refer to the [Collaborative editing policies](https://doc.ibexa.co/en/latest/permissions/policies/#content-collaborative-editing) for guidance on editing without a strict workflow. ## Multi-file upload Creating content through multi-file upload is treated in the same way as regular creation. To enable upload, you need you set the following permissions: - `content/create` - `content/read` - `content/publish` You can control what content items can be uploaded and where by using imitations on the `content/create` and `content/publish` policies. A location limitation limits the uploading to a specific location in the tree. A content type limitation controls the content types that are allowed. For example, you can set the location limitation on a **Pictures** Folder, and add a content type limitation that only allows content items of type **Image**. This ensures that only files of type `image` can be uploaded, and only to the **Pictures** Folder. ## Taxonomies You can control which users or user groups can work with taxonomies. To let users create and assign taxonomy entries, set the following permissions: - `taxonomy/assign` to allow user to tag and untag content - `taxonomy/read` to see the Taxonomy interface - `taxonomy/manage` to create, edit and delete tags With limitations, you can configure whether permissions apply to Tags, product categories, or both. ## Register users To allow anonymous users to register through the `/register` route, grant the `user/register` policy to the Anonymous user group. ## Admin To access the [administration panel](https://doc.ibexa.co/en/latest/administration/admin_panel/admin_panel/index.md) in the back office, the User must have the `setup/administrate` policy. This allows the User to view the languages and content types. Additional policies are needed for each section of the Admin. ### System Information - `setup/system_info` to view the System Information tab ### Sections - `section/view` to see and access the section list - `section/edit` to add and edit sections - `section/assign` to assign sections to content ### Languages - `content/translations` to add and edit languages ### Content types/action - `content type/create`, `content type/update`, `content type/delete` to add, modify and remove content types ### Object states - `state/administrate` to view a list of object states, add and edit them - `state/assign` to assign Objects states to content ### Roles - `role/read` to view the list of roles in Admin - `role/create`, `role/update`, `role/assign` and `role/delete` to manage roles ### Users - `content/view` to view the list of users Users are treated like other content, so to create and modify them, the user needs to have the same permissions as for managing other content items. ## Product catalog You can control to what extend users can access the product catalog and all its related parts. ### Product type To create or edit product types, a user needs to have access to attributes and attribute groups. Set the following permissions to allow such access: - `product_type/create` - `product_type/view` - `product_type/edit` ### Product item When a product is created, a product item and a content item are also generated. Permissions for the product catalog override permissions for content, therefore, users without permissions for content can still manage products. - `product/create` - `product/view` - `product/edit` ## Commerce (Commerce) To control which commerce functionalities are available to store users, you can grant or prevent them access to individual components. Out of the box, Ibexa Commerce comes with the *Storefront User* role that is assigned to anonymous users and grants them the following permissions: - `product/view`, `product_type/view` and `catalog/view`, to allow them to view a product list and product details - `cart/view`, `cart/create` and `cart/edit` with the `CartOwner` limitation set to `self`, to allow them to add items to their own shopping cart, modify their cart, and delete it - `checkout/view`, `checkout/create`, `checkout/update` and `checkout/delete`, to allow them to proceed to checkout and interact with the checkout process You can modify the default roles by preventing anonymous users from being able to proceed with the checkout process, and creating the *Registered Buyer* role that enables logged-in users to purchase products. You could do this by moving permissions that relate to checkout from the *Storefront User* role to the *Registered Buyer* role, and granting *Registered Buyer* with the `user/register` and `user/login` permissions which control access to registration and login. See below for a detailed listing of permissions that apply to Commerce, together with their meaning. > **Note: Owner limitation** > > For anonymous users, orders, shipments, and/or payments are saved with a 'null' user reference. Therefore, when you apply the `Owner/self` limitation to any of the permissions below, anonymous users aren't allowed to interact with any of these entities. ### Cart Set the following permissions to decide what actions are available when users interact with carts: - `cart/view` - to allow user to view their cart - `cart/delete` - to delete cart, for example, after successful checkout - `cart/create` - to create a new cart - `cart/edit` - to allow user to add products to their cart To further control access to a cart, you can use the `CartOwner` limitation and set its value to `self` This way users can only interact with their own carts. ### Checkout Set the following permissions to decide what actions are available when users interact with checkout: - `checkout/view` - to control user access to checkout - `checkout/create` - to allow starting the checkout process, by proceeding from cart - `checkout/update` - to allow users to modify existing information, for example item quantity - `checkout/delete` - to delete checkout ### Discount management Set the following permissions to decide what actions are available when users interact with [discounts](https://doc.ibexa.co/en/latest/discounts/discounts/index.md) in the back office: - `discount/create` - to allow the user to create a new discount - `discount/update` - to allow the user to change the parameters of an existing discount - `discount/view` - to allow the user to view discounts data - `discount/delete` - to delete an existing discount - `discount/enable` - to allow the user to enable an existing discount - `discount/disable` - to allow the user to disable an existing discount To further control access to a discount, you can use the `DiscountOwner` limitation and set its value to `self`. This way users can only interact with their own discounts. Store users do not need any permissions to use discounts in the buying process. ### Order management Set the following permissions to decide what actions are available when users interact with orders: - `order/create` - to allow the user to create a new order - `order/view` - to allow the user to view orders - `order/update` - to allow the user to change status of an existing order - `order/cancel` - to allow the user to cancel an existing order To further control access to an order, you can use the `OrderOwner` limitation and set its value to `self`. This way users can only interact with their own orders. ### Shipping management Set the following permissions to decide what actions are available when users interact with shipping methods and shipments. #### Shipping methods - `shipping_method/create` - to allow the user to create a new shipping method - `shipping_method/view` - to allow the user to view shipping methods - `shipping_method/edit` - to allow the user to modify an existing shipping method - `shipping_method/delete` - to allow the user to delete an existing shipping method #### Shipments - `shipment/create` - to allow the user to create a new shipment - `shipment/view` - to allow the user to view shipments - `shipment/update` - to allow the user to change status of an existing shipment - `shipment/delete` - to allow the user to cancel an existing shipment To further control access to a shipment, you can use the `ShipmentOwner` limitation and set its value to `self`. This way users can only interact with their own shipments. ### Payment management Set the following permissions to decide what actions are available when users interact with payment methods and payments. #### Payment methods - `payment_method/create` - to allow the user to create a new payment method - `payment_method/view` - to allow the user to view payment methods - `payment_method/edit` - to allow the user to modify an existing payment method - `payment_method/delete` - to allow the user to delete an existing payment method #### Payments - `payment/create` - to allow the user to create a new payment - `payment/view` - to allow the user to view payments - `payment/edit` - to allow the user to modify an existing payment - `payment/delete` - to allow the user to delete an existing payment To further control access to a payment, you can use the `PaymentOwner` limitation and set its value to `self`. This way users can only interact with their own payments. # Policies Policies are the main building block of the permissions system. Each role you assign to user or user group consists of policies which define, which parts of the application or website the user has access to. ## Available policies ### Access to all functions | Module | Function | Effect | Possible limitations | | ------ | -------- | ----------------------------------------------------------- | -------------------- | | `*` | `*` | all modules, all functions: grant all available permissions | | > **Tip: Tip** > > For each module, all functions can be given without limitation. For example, `content/*` gives access to all functions of the `content` module, even future ones. ### Administration and user management #### Activity log | Module | Function | Effect | Possible Limitations | | -------------- | -------- | -------------------- | ------------------------------------------------------------------------------------------------------------------ | | `activity_log` | `read` | access activity list | [ActivityLogOwner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#activity-log-owner-limitation) | #### AI actions | Module | Function | Effect | Possible Limitations | | ---------------------- | --------- | ---------------------- | -------------------- | | `action_configuration` | `view` | view AI Action | | | | `create` | create a new AI action | | | | `edit` | edit an AI action | | | | `delete` | delete an AI action | | | | `execute` | execute an AI action | | #### Customer groups | Module | Function | Effect | Possible limitations | | ---------------- | -------- | ----------------------- | -------------------- | | `customer_group` | `create` | create a customer group | | | | `delete` | delete a customer group | | | | `edit` | edit a customer group | | | | `view` | view customer groups | | #### Personalization | Module | Function | Effect | Possible limitations | | ----------------- | -------- | ----------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | `personalization` | `edit` | modify scenario configuration for selected SiteAccesses | [Personalization access](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#personalization-access-limitation) | | | `view` | view scenario configuration and results for selected SiteAccesses | [Personalization access](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#personalization-access-limitation) | #### Roles | Module | Function | Effect | Possible limitations | | ------ | -------- | -------------------------------------------------------------------------- | -------------------- | | `role` | `assign` | assign roles to users and user groups | | | | `create` | create new roles | | | | `delete` | delete roles | | | | `read` | view the roles list in Admin. Required for all other role-related policies | | | | `update` | modify existing roles | | #### Setup | Module | Function | Effect | Possible limitations | | ------- | -------------- | -------------------------------------------- | -------------------- | | `setup` | `administrate` | access Admin | | | | `install` | unused | | | | `setup` | unused | | | | `system_info` | view the **System Information** tab in Admin | | #### Sites (Experience) (Commerce) | Module | Function | Effect | Possible limitations | | ------ | --------------- | ---------------------------------------------------------------------------------------- | -------------------- | | `site` | `change_status` | change status of the public accesses of sites to `Live` or `Offline` in the Site Factory | | | | `create` | create sites in the Site Factory | | | | `delete` | delete sites from the Site Factory | | | | `edit` | edit sites in the Site Factory | | | | `update` | update sites in the Site Factory | | | | `view` | view the "Sites" in the top navigation | | #### Users | Module | Function | Effect | Possible limitations | | ------ | ------------- | ------------------------------------------------ | -------------------- | | `user` | `activation` | unused | | | | `invite` | create and send invitations to create an account | | | | `login` | log in to the application | | | | `password` | unused | | | | `preferences` | access and set user preferences | | | | `register` | register using the `/register` route | | | | `selfedit` | unused | | ### Commerce #### Cart (Commerce) | Module | Function | Effect | Possible limitations | | ------ | -------- | ------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | | `cart` | `create` | create a cart | [CartOwner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#cart-owner-limitation) | | | `delete` | delete cart, for example, after successful checkout | [CartOwner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#cart-owner-limitation) | | | `edit` | change cart metadata (name, currency, owner), add/remove cart items | [CartOwner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#cart-owner-limitation) | | | `view` | view a cart | [CartOwner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#cart-owner-limitation) | #### Checkout (Commerce) | Module | Function | Effect | Possible limitations | | ---------- | -------- | ------------------------------------------------------------------- | -------------------- | | `checkout` | `create` | create new checkout, for example, after workflow fails to complete | | | | `delete` | delete checkout, for example, after workflow completes successfully | | | | `update` | change currency, quantity | | | | `view` | access checkout | | #### Currencies and regions | Module | Function | Effect | Possible limitations | | ---------- | ---------- | ----------------- | -------------------- | | `commerce` | `currency` | manage currencies | | | | `region` | manage regions | | #### Discounts (Commerce) The [discount](https://doc.ibexa.co/en/latest/discounts/discounts/index.md) policies decide which actions can be executed by given user or user group. > **Caution: Customers and discount policies** > > Customers don't need any policies to use the discounts on the [storefront](https://doc.ibexa.co/en/latest/commerce/storefront/storefront/index.md). Even the `discount/view` policy would allow them to access all the discount details, including the coupon codes to activate them, which could lead to system abuse. | Module | Function | Effect | Possible limitations | | ---------- | --------- | -------------------------------------- | ----------------------------------------------------------------------------------------------------------- | | `discount` | `create` | create a discount | [DiscountOwner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#discount-owner-limitation) | | | `update` | modify discount parameters | [DiscountOwner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#discount-owner-limitation) | | | `view` | view discounts (including its details) | [DiscountOwner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#discount-owner-limitation) | | | `delete` | delete a discount | [DiscountOwner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#discount-owner-limitation) | | | `enable` | enable a discount | [DiscountOwner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#discount-owner-limitation) | | | `disable` | disable a discount | [DiscountOwner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#discount-owner-limitation) | #### Orders (Commerce) | Module | Function | Effect | Possible limitations | | ------- | -------- | ------------------------- | ----------------------------------------------------------------------------------------------------- | | `order` | `cancel` | cancel an order | [OrderOwner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#order-owner-limitation) | | | `create` | create an order | [OrderOwner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#order-owner-limitation) | | | `update` | change status of an order | [OrderOwner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#order-owner-limitation) | | | `view` | view orders | [OrderOwner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#order-owner-limitation) | #### Payments (Commerce) | Module | Function | Effect | Possible limitations | | --------- | -------- | ---------------- | -------------------------------------------------------------------------------------------------------- | | `payment` | `create` | create a payment | [PaymentOwner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#paymentowner-limitation) | | | `delete` | delete a payment | [PaymentOwner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#paymentowner-limitation) | | | `edit` | modify a payment | [PaymentOwner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#paymentowner-limitation) | | | `view` | view payments | [PaymentOwner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#paymentowner-limitation) | #### Payment methods (Commerce) | Module | Function | Effect | Possible limitations | | ---------------- | -------- | ----------------------- | -------------------- | | `payment_method` | `create` | create a payment method | | | | `delete` | delete a payment method | | | | `edit` | modify a payment method | | | | `view` | view payment methods | | #### Segments (Commerce) | Module | Function | Effect | Possible limitations | | --------- | ---------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------- | | `segment` | `assign_to_user` | assign segments to users | [Segment Group](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#segment-group-limitation) | | | `create` | create segments | [Segment Group](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#segment-group-limitation) | | | `read` | load segment information | [Segment Group](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#segment-group-limitation) | | | `remove` | remove segments | [Segment Group](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#segment-group-limitation) | | | `update` | update segments | [Segment Group](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#segment-group-limitation) | #### Segment groups (Commerce) | Module | Function | Effect | Possible limitations | | --------------- | -------- | ------------------------------ | -------------------- | | `segment_group` | `create` | create segment groups | | | | `read` | load segment group information | | | | `remove` | remove segment groups | | | | `update` | update segment groups | | #### Shipments (Commerce) | Module | Function | Effect | Possible limitations | | ---------- | -------- | --------------------------- | ----------------------------------------------------------------------------------------------------------- | | `shipment` | `create` | create a shipment | [ShipmentOwner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#shipment-owner-limitation) | | | `delete` | delete a shipment | [ShipmentOwner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#shipment-owner-limitation) | | | `update` | change status of a shipment | [ShipmentOwner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#shipment-owner-limitation) | | | `view` | view shipments | [ShipmentOwner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#shipment-owner-limitation) | #### Shipping methods (Commerce) | Module | Function | Effect | Possible limitations | | ----------------- | -------- | ------------------------ | -------------------- | | `shipping_method` | `create` | create a shipping method | | | | `delete` | delete a shipping method | | | | `update` | modify a shipping method | | | | `view` | view shipping methods | | #### Shopping lists (LTS Update) (Commerce) | Module | Function | Effect | Possible limitations | | --------------- | -------- | ---------------------- | -------------------------------------------------------------------------------------------------------------- | | `shopping_list` | `create` | create a shopping list | [ShoppingListOwner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#shopping-list-limitation) | | | `delete` | delete a shopping list | [ShoppingListOwner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#shopping-list-limitation) | | | `edit` | modify a shopping list | [ShoppingListOwner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#shopping-list-limitation) | | | `view` | view shopping lists | [ShoppingListOwner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#shopping-list-limitation) | ### Content management #### Content | Module | Function | Effect | Possible limitations | | --------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `content` | `cleantrash` | empty the Trash (even when the User doesn't have access to individual content items) | | | | `create` | create new content. Note: even without this policy the user is able to enter edit mode, but cannot finalize work with the content item. | [Content type](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#content-type-limitation)[Section](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#section-limitation)[Location](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#location-limitation)[Subtree](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#subtree-limitation)[Language](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#language-limitation)[Owner of Parent](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#owner-of-parent-limitation)[Content type Group of Parent](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#content-type-group-of-parent-limitation)[Content type of Parent](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#content-type-of-parent-limitation)[Parent Depth](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#parent-depth-limitation)[Field Group](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#field-group-limitation)[Change Owner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#change-owner-limitation) | | | `diff` | unused | | | | `edit` | edit existing content | [Content type](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#content-type-limitation)[Section](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#section-limitation)[Owner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#owner-limitation)[Content type Group](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#content-type-group-limitation)[Location](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#location-limitation)[Subtree](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#subtree-limitation)[Language](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#language-limitation)[Object State](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#object-state-limitation)[Workflow Stage](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#workflow-stage-limitation)[Field Group](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#field-group-limitation)[Version Lock](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#version-lock-limitation)[Change Owner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#change-owner-limitation) | | | `hide` | hide and reveal content locations | [Content type](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#content-type-limitation)[Section](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#section-limitation)[Owner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#owner-limitation)[Content type Group](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#content-type-group-limitation)[Location](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#location-limitation)[Subtree](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#subtree-limitation)[Language](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#language-limitation) | | | `manage_locations` | remove locations and send content to Trash | [Content type](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#content-type-limitation)[Section](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#section-limitation)[Owner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#owner-limitation)[Subtree](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#subtree-limitation)[Object State](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#object-state-limitation) | | | `pendinglist` | unused | | | | `publish` | publish content. Without this Policy, the User can only save drafts or send them for review (in Ibexa Experience) | [Content type](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#content-type-limitation)[Section](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#section-limitation)[Owner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#owner-limitation)[Content type Group](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#content-type-group-limitation)[Location](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#location-limitation)[Subtree](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#subtree-limitation)[Language](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#language-limitation)[Object State](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#object-state-limitation)[Workflow Stage](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#workflow-stage-limitation) | | | `read` | view the content both in front and back end | [Content type](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#content-type-limitation)[Section](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#section-limitation)[Owner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#owner-limitation)[Content type Group](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#content-type-group-limitation)[Location](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#location-limitation)[Subtree](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#subtree-limitation)[Object State](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#object-state-limitation) | | | `remove` | remove locations and send content to Trash | [Content type](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#content-type-limitation)[Section](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#section-limitation)[Owner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#owner-limitation)[Location](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#location-limitation)[Subtree](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#subtree-limitation)[Object State](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#object-state-limitation)[Language](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#language-limitation) | | | `restore` | restore content from Trash | | | | `reverserelatedlist` | see all content that a content item relates to (even when the User isn't allowed to view it as an individual content items) | [Content type](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#content-type-limitation)[Section](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#section-limitation) | | | `translate` | unused | [Content type](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#content-type-limitation)[Section](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#section-limitation)[Owner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#owner-limitation)[Location](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#location-limitation)[Subtree](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#subtree-limitation)[Language](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#language-limitation) | | | `translations` | manage the language list in Admin | | | | `unlock` | unlock drafts locked to a user for performing actions | [Owner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#owner-limitation)[Content type Group](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#content-type-group-limitation)[Subtree](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#subtree-limitation)[Language](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#language-limitation)[Version Lock](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#version-lock-limitation) | | | `urltranslator` | manage URL aliases of a content item | | | | `versionread` | view content after publishing, and to preview any content in the Site mode | [Content type](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#content-type-limitation)[Section](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#section-limitation)[Owner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#owner-limitation)Status[Location](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#location-limitation)[Subtree](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#subtree-limitation)[Object State](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#object-state-limitation) | | | `versionremove` | remove archived content versions | [Content type](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#content-type-limitation)[Section](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#section-limitation)[Owner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#owner-limitation)Status[Location](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#location-limitation)[Subtree](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#subtree-limitation)[Object State](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#object-state-limitation) | | | `view_embed` | view content embedded in another content item (even when the User isn't allowed to view it as an individual content item) | [Content type](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#content-type-limitation)[Section](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#section-limitation)[Owner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#owner-limitation)[Location](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#location-limitation)[Subtree](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#subtree-limitation) | #### Content collaborative editing | Module | Function | Effect | Possible limitations | | --------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `content` | `share` | share content drafts with internal and external users through [collaborative editing](https://doc.ibexa.co/en/latest/content_management/collaborative_editing/collaborative_editing/index.md) | [Owner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#collaborative-editing-owner-limitation)[PublicLink](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#collaborative-editing-publiclink-limitation)[Scope](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#collaborative-editing-scope-limitation) | | `rte` | `edit` | use [Real-time editing](https://doc.ibexa.co/en/latest/content_management/collaborative_editing/collaborative_editing_guide/#real-time-editing) | | #### Content types | Module | Function | Effect | Possible limitations | | ------- | -------- | ------------------------------------------------------------------------ | -------------------- | | `class` | `create` | create new content types. Also required to edit exiting content types | | | | `delete` | delete content types | | | | `update` | modify existing content types. Also required to create new content types | | #### Sections | Module | Function | Effect | Possible limitations | | --------- | -------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `section` | `assign` | assign Sections to content | [content type](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#content-type-limitation)[Section](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#section-limitation)[Owner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#owner-limitation)[New Section](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#new-section-limitation) | | | `edit` | edit existing Sections and create new ones | | | | `view` | view the Sections list in Admin. Required for all other section-related policies | | #### Object States | Module | Function | Effect | Possible limitations | | ------- | -------------- | ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `state` | `assign` | assign object states to content items | [Content type](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#content-type-limitation)[Section](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#section-limitation)[Owner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#owner-limitation)[Content type Group](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#content-type-group-limitation)[Location](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#location-limitation)[Subtree](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#subtree-limitation)[Object State](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#object-state-limitation)[New State](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#new-state-limitation) | | | `administrate` | view, add and edit object states | | #### Taxonomy | Module | Function | Effect | Possible limitations | | ---------- | -------- | ----------------------------- | -------------------- | | `taxonomy` | `assign` | tag or untag content | | | | `manage` | create, edit, and delete tags | | | | `read` | view the Taxonomy interface | | #### Workflow and version comparison | Module | Function | Effect | Possible limitations | | ------------ | -------------- | -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | | `comparison` | `view` | view version comparison | | | `workflow` | `change_stage` | change stage in the specified workflow | [Workflow Transition](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#workflow-transition-limitation) | ### Product catalog #### Catalogs | Module | Function | Effect | Possible limitations | | --------- | -------- | ---------------- | -------------------- | | `catalog` | `create` | create a catalog | | | | `delete` | delete a catalog | | | | `edit` | edit a catalog | | | | `view` | view catalogs | | #### Products | Module | Function | Effect | Possible limitations | | --------- | -------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `product` | `create` | create a product | [Product Type](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#product-type-limitation)[Language](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#language-limitation) | | | `delete` | delete a product | [Product Type](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#product-type-limitation) | | | `edit` | edit a product | [Product Type](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#product-type-limitation)[Language](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#language-limitation) | | | `view` | view products listed in the product catalog | [Product Type](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#product-type-limitation) | > **Warning: Warning** > > The `ProductType` limitation can't be used when using [Quable](https://doc.ibexa.co/en/latest/product_catalog/quable/quable/index.md). #### Product collaborative editing | Module | Function | Effect | Possible limitations | | --------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `product` | `share` | share products with internal and external users through [collaborative editing](https://doc.ibexa.co/en/latest/content_management/collaborative_editing/collaborative_editing/index.md) | [Owner](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#collaborative-editing-owner-limitation)[PublicLink](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#collaborative-editing-publiclink-limitation)[Scope](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#collaborative-editing-scope-limitation) | #### Product types | Module | Function | Effect | Possible limitations | | -------------- | -------- | ---------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | | `product_type` | `create` | create a product type, a new attribute, a new attribute group, and add translation to product type and attribute | [Product Type](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#product-type-limitation) | | | `delete` | delete a product type, attribute, attribute group | | | | `edit` | edit a product type, attribute, attribute group | [Product Type](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#product-type-limitation) | | | `view` | view product types, attributes and attribute groups | | > **Warning: Warning** > > The `ProductType` limitation can't be used when using [Quable](https://doc.ibexa.co/en/latest/product_catalog/quable/quable/index.md). ## Combining policies Policies on one role are connected with the *and* relation, not *or*, so when policy has more than one limitation, all of them have to apply. If you want to combine more than one limitation with the *or* relation, not *and*, you can split your policy in two, each with one of these limitations. # Limitations Limitations are part of the permissions system. They limit the access granted to users by [policies](https://doc.ibexa.co/en/latest/permissions/permission_overview/index.md). While a policy grants the user access to a function, Limitations narrow it down by different criteria. Limitations consist of two parts: - `Limitation` (Value) - `LimitationType` Certain limitations also serve as role limitations, which means they can be used to limit the rights of a role assignment. Currently, this covers [subtree of location](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#subtree-limitation), [Section](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#section-limitation) and [Personalization access](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#personalization-access-limitation) limitations. `Limitation` represents the value, while `LimitationType` deals with the business logic surrounding how it actually works and is enforced. `LimitationTypes` have two modes of operation in regard to permission logic (see [`Ibexa\Contracts\Core\Limitation`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Limitation-Type.html) interface for more info): | Method | Use | | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `evaluate` | Evaluates if the User has access to a given object in a certain context (for instance the context can be locations when the object is `Content`), under the condition of the `Limitation` value(s). | | `getCriterion` | Generates a `Criterion` based on `Limitation` value and current user which `SearchService` by default applies to Search Criteria for filtering search based on permissions. | ## Limitation reference See [Limitation reference](https://doc.ibexa.co/en/latest/permissions/limitation_reference/index.md) for detailed information about individual limitations. # Limitation reference ## Blocking limitation A generic limitation type to use when no other limitation has been implemented. Without any limitation assigned, a `LimitationNotFoundException` is thrown. It's called "blocking" because it always informs the permissions system that the user doesn't have access to any policy the limitation is assigned to, making the permissions system move on to the next policy. ### Possible values | Value | UI value | Description | | --------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `` | `` | This is a generic limitation which doesn't validate the values provided to it. Make sure that you validate the values passed to this limitation in your own logic. | ### Configuration As this is a generic limitation, you can configure your custom limitations to use it. Out of the box FunctionList uses it in the following way: ``` # FunctionList is an ezjscore limitation, it only applies to ezjscore policies not used by # API/platform stack, so configure to use Blocking limitation to avoid LimitationNotFoundException ibexa.api.role.limitation_type.function_list: class: Ibexa\Core\Limitation\BlockingLimitationType arguments: ['FunctionList'] tags: - {name: ibexa.permissions.limitation_type, alias: FunctionList} ``` ## Activity log Owner limitation The Activity log Owner (`ActivityLogOwner`) limitation specifies if a user can see only their own [recent activity](https://doc.ibexa.co/en/latest/administration/recent_activity/recent_activity/index.md) log entries, and not entries from other users. | Value | UI value | Description | | ----- | --------------- | ------------------------------------------------------------ | | `1` | "Only own logs" | Current user can only access their own activity log entries. | ## Cart Owner limitation The Cart Owner (`CartOwner`) limitation specifies whether the user can modify a cart. ### Possible values | Value | UI value | Description | | ------ | -------- | ------------------------------------------------------- | | "self" | "self" | Only the user who is the owner of the cart gets access. | | `null` | none | User can access all carts. | ## Change Owner limitation The Change Owner (`ChangeOwner`) limitation specifies whether the user can change the owner of a content item. ### Possible values | Value | UI value | Description | | ----- | -------- | ---------------------------------------------- | | `1` | "Forbid" | The user cannot change owner of a content item | ## Collaborative editing limitations The Collaborative editing limitations specify how the user can use the [Collaborative editing](https://doc.ibexa.co/en/latest/content_management/collaborative_editing/collaborative_editing/index.md) feature. ### Collaborative editing Owner limitation The Owner limitation specifies whose drafts the user can share. If not specified, user can share: - their drafts - drafts they have been invited to collaborate on #### Possible values | Value | UI value | Description | | ------ | -------- | ---------------------------------------- | | "self" | "self" | User can only share drafts that they own | ### Collaborative editing Scope limitation The Scope limitation specifies whether the user can share the preview and editing view with other users. #### Possible values | Value | UI value | Description | | ------ | -------- | ------------------------------------------- | | "edit" | "Edit" | User can invite other users to edit | | "view" | "View" | User can share the preview with other users | ### Collaborative editing PublicLink limitation The Public Link (`PublicLink`) limitation specifies whether the user can manage the settings of the shareable preview link. #### Possible values | Value | UI value | Description | | ----- | -------- | ------------------------------ | | "Off" | "off" | User can't manage the settings | | "On" | "on" | User can manage the settings | ## Discount Owner limitation (Commerce) The Discount Owner (`DiscountOwner`) limitation specifies whether the user can interact with a [discount](https://doc.ibexa.co/en/latest/discounts/discounts/index.md). ### Possible values | Value | UI value | Description | | ------ | -------- | ----------------------------------------------------------- | | "self" | "self" | Only the user who is the owner of the discount gets access. | ## Content type Group limitation The Content Type Group (`UserGroup`) limitation specifies that only users with at least one common *direct* user group with the owner of content get the selected access right. ### Possible values | Value | UI value | Description | | ----- | -------- | -------------------------------------------------------------------------------------- | | `1` | "self" | Only a user who has at least one common *direct* user group with the owner gets access | ## Content type Group of Parent limitation The Content Type Group of Parent (`ParentUserGroupLimitation`) limitation specifies that only Users with at least one common *direct* user group with the owner of the parent location of a content item get a certain access right, used by `content/create` permission. ### Possible values | Value | UI value | Description | | ----- | -------- | --------------------------------------------------------------------------------------------------------- | | `1` | "self" | Only a user who has at least one common *direct* user group with owner of the parent location gets access | ## Content type limitation The Content Type (`ContentType`) limitation specifies whether the user has access to content with a specific content type. ### Possible values | Value | UI value | Description | | ------------------ | -------------------- | ------------------------------------------------- | | `` | `` | All valid content type IDs can be set as value(s) | ## Content type of Parent limitation The Content Type of Parent (`ParentContentType`) limitation specifies whether the user has access to content whose parent location contains a specific content type, used by `content/create`. This limitation combined with `ContentType` limitation allows you to define business rules like allowing users to create "Blog Post" within a "Blog." If you also combine it with `Owner of Parent` limitation, you effectively limit access to create Blog Posts in the users' own Blogs. ### Possible values | Value | UI value | Description | | ------------------ | -------------------- | ------------------------------------------------- | | `` | `` | All valid content type IDs can be set as value(s) | ## Field Group limitation (Experience) (Commerce) A Field Group (`FieldGroup`) limitation specifies whether the user can work with content fields belonging to a specific group. A user with this limitation is allowed to edit fields belonging to the indicated group. Otherwise, the fields are inactive and filled with the default value (if set). ### Possible values | Value | UI value | Description | | ------------------------- | ------------------------- | -------------------------------------------------------- | | `` | `` | All valid field group identifiers can be set as value(s) | ## Language limitation A Language (`Language`) limitation specifies whether the user has access to work on the specified translation. A user with this limitation is allowed to: - Create new content with the given translation(s) only. This only applies to creating the first version of a content item. - Edit content by adding a new translation or modifying an existing translation. - Publish content only when it results in adding or modifying an allowed translation. - Delete content only when it contains a translation into the specified language. ### Possible values | Value | UI value | Description | | ----------------- | --------------------- | ----------------------------------------------- | | `` | `` | All valid language codes can be set as value(s) | ## Location limitation A location (`Location`) limitation specifies whether the user has access to content with a specific location, in case of `content/create` the parent location is evaluated. ### Possible values | Value | UI value | Description | | --------------- | ----------------- | --------------------------------------------- | | `` | `` | All valid location IDs can be set as value(s) | ## New Section limitation A New Section (`NewSection`) limitation specifies whether the user has access to assigning content to a given section. In the `section/assign` policy you can combine this with section limitation to limit both from and to values. ### Possible values | Value | UI value | Description | | -------------- | ---------------- | -------------------------------------------- | | `` | `` | All valid session IDs can be set as value(s) | ## New State limitation A New State (`NewObjectState`) limitation specifies whether the user has access to (assigning) a given object state to content. In the `state/assign` policy you can combine this with State limitation to limit both from and to values. ### Possible values | Value | UI value | Description | | ------------ | -------------- | ------------------------------------------ | | `` | `` | All valid state IDs can be set as value(s) | ## Object State limitation The Object State (`ObjectState`) limitation specifies whether the user has access to content with a specific object state. ### Possible values | Value | UI value | Description | | ------------------ | -------------------- | ------------------------------------------------- | | `` | `` | All valid Object state IDs can be set as value(s) | ## Order Owner limitation The Order Owner (`OrderOwner`) limitation specifies whether the user can modify an order. ### Possible values | Value | UI value | Description | | ------ | -------- | --------------------------------------- | | "self" | "self" | Users can access only their own orders. | ## Owner limitation The Owner (`Owner`) limitation specifies that only the owner of the content item gets the selected access right. ### Possible values | Value | UI value | Description | | ----- | --------- | ----------------------------------------------------------------------------------------------------- | | `1` | "self" | Only the user who is the owner gets access | | `2` | "session" | Deprecated and works exactly like "self" in public PHP API since it has no knowledge of user Sessions | ## Owner of Parent limitation The Owner of Parent (`ParentOwner`) limitation specifies that only the users who own all parent locations of a content item get a certain access right, used for `content/create` permission. ### Possible values | Value | UI value | Description | | ----- | --------- | ----------------------------------------------------------------------------------------------------- | | `1` | "self" | Only the user who is the owner of all parent locations gets access | | `2` | "session" | Deprecated and works exactly like "self" in public PHP API since it has no knowledge of user Sessions | ## Parent Depth limitation The Parent Depth (`ParentDepth`) limitation specifies whether the user has access to creating content under a parent location within a specific depth of the tree, used for `content/create` permission. ### Possible values | Value | UI value | Description | | ------- | -------- | ----------------------------------------- | | `` | `` | All valid integers can be set as value(s) | ## PaymentOwner limitation The Payment Owner (`PaymentOwner`) limitation specifies whether the user can modify a payment. ### Possible values | Value | UI value | Description | | ------ | -------- | ----------------------------------------- | | "self" | "self" | Users can access only their own payments. | | "all" | none | Users can access all payments. | ## Personalization access limitation The Personalization limitation specifies the SiteAccesses for which the user can view or modify the scenario configuration. ## Product Type limitation The Product Type (`ProductType`) limitation specifies whether the user has access to products belonging to a specific product type. > **Warning: Warning** > > The `ProductType` limitation can't be used when using [Quable](https://doc.ibexa.co/en/latest/product_catalog/quable/quable/index.md). ### Possible values | Value | UI value | Description | | ------------------ | -------------------- | ------------------------------------------------- | | `` | `` | All valid content type IDs can be set as value(s) | ## Section limitation The Section (`Section`) limitation specifies whether the user has access to content within a specific section. This limitation can be used as a role limitation. ### Possible values | Value | UI value | Description | | -------------- | ---------------- | -------------------------------------------- | | `` | `` | All valid session IDs can be set as value(s) | ## Segment group limitation (Experience) (Commerce) The segment group (`SegmentGroup`) limitation specifies whether the user has access segments within a specific segment group. This limitation can be used as a role limitation. ### Possible values | Value | UI value | Description | | -------------------- | ---------------------- | --------------------------------------------------- | | `` | `` | All valid segment group IDs can be set as value(s). | ## Shopping list limitation (LTS Update) (Commerce) The Shopping List Owner (`ShoppingListOwner`) limitation specifies whether the user can modify a [shopping list](https://doc.ibexa.co/en/latest/commerce/shopping_list/shopping_list/index.md). ### Possible values | Value | UI value | Description | | ------ | -------- | ---------------------------------------------------------------- | | "self" | "self" | Only the user who is the owner of the shopping list gets access. | | `null` | none | User can access all shopping lists. | ## SiteAccess limitation The SiteAccess (`SiteAccess`) limitation specifies to which SiteAccesses a certain permission applies, used by `user/login`. ### Possible values | Value | UI value | Description | | ------------------- | ------------------- | -------------------------------------------------------------------------------------------------------------------- | | `` | `` | Hash is calculated in the following way in legacy in default 64bit mode: `sprintf( '%u', crc32( $siteAccessName ) )` | ### Legacy compatibility notes `SiteAccess` limitation is deprecated and isn't used actively in public PHP API, but is allowed for being able to read / create limitations for legacy. ## Shipment Owner limitation The Shipment Owner (`ShipmentOwner`) limitation specifies whether the user can modify a shipment. ### Possible values | Value | UI value | Description | | ------ | -------- | ------------------------------------------ | | "self" | "self" | Users can access only their own shipments. | ## Subtree limitation The subtree (`Subtree`) limitation specifies whether the user has access to content within a specific subtree of location, in case of `content/create` the parent subtree of location is evaluated. This limitation can be used as a role limitation. ### Possible values | Value | UI value | Description | | ----------------------- | ----------------- | ------------------------------------------------------- | | `` | `` | All valid location `pathStrings` can be set as value(s) | ### Usage notes For more information on how to restrict user's access to part of the subtree, see [the example in the Admin management section](https://doc.ibexa.co/en/latest/permissions/permission_use_cases/#restrict-editing-to-part-of-the-tree). ## Taxonomy limitation The taxonomy (`Taxonomy`) limitation specifies with which [taxonomies](https://doc.ibexa.co/en/latest/content_management/taxonomy/taxonomy/index.md) (tags, product categories, or custom ones) user can interact. The supported policies are: - `taxonomy/read` - `taxonomy/manage` - `taxonomy/assign` ### Possible values | Value | UI value | Description | | -------------------- | -------------- | -------------------------- | | Taxonomy identifiers | Taxonomy names | List of allowed taxonomies | ## Taxonomy Subtree limitation The taxonomy subtree (`TaxonomySubtree`) limitation specifies whether the user has access to a specific subtree within the [taxonomy](https://doc.ibexa.co/en/latest/content_management/taxonomy/taxonomy/index.md) tree. Once a tag is selected, user can interact with it and all the child tags below it in the taxonomy tree. In addition, it grants read-only access to all the parent tags (up to the taxonomy root) so that the user can see the context. The supported policies are: - `taxonomy/read` - `taxonomy/manage` - `taxonomy/assign` ### Possible values | Value | UI value | Description | | ------- | ------------- | ----------------------------- | | Tag IDs | Selected tags | All valid Tag IDs are allowed | ## Version Lock limitation The Version Lock (`VersionLock`) limitation specifies whether the user can perform actions, for example, edit or unlock, on content items that are in a workflow. This limitation can be used as a role limitation. ### Possible values | Value | UI value | Description | | -------- | --------------- | ----------------------------------------------------------------------------------------------------------- | | `userId` | "Assigned only" | Users can perform actions only on content items that are assigned to them or not assigned to anybody. | | `null` | none | Users can perform actions on all drafts, regardless of the assignments or whether drafts are locked or not. | ## Workflow Stage limitation The Workflow Stage (`WorkflowStage`) limitation specifies whether the user can edit content in a specific workflow stage. ### Possible values The limitation takes as values stages configured for the workflow. ## Workflow Transition limitation The Workflow Transition (`WorkflowTransition`) limitation specifies whether the user can move the content in a workflow through a specific transition. ### Possible values The limitation takes as values transitions between stages configured for the workflow. # Custom policies The content repository uses [roles and policies](https://doc.ibexa.co/en/latest/permissions/permissions/index.md) to give users access to different functions of the system. Any bundle can expose available policies via a `PolicyProvider` which can be added to IbexaCoreBundle's [service container](https://doc.ibexa.co/en/latest/api/php_api/php_api/#service-container) extension. ## PolicyProvider A `PolicyProvider` object provides a hash containing declared modules, functions and limitations. - Each policy provider provides a collection of permission *modules*. - Each module can provide *functions* (for example, in `content/read`, "content" is the module, and "read" is the function) - Each function can provide a collection of limitations. First level key is the module name which is limited to characters within the set `A-Za-z0-9_`, value is a hash of available functions, with function name as key. Function value is an array of available limitations, identified by the alias declared in `LimitationType` service tag. If no limitation is provided, value can be `null` or an empty array. ``` [ "content" => [ "read" => ["Class", "ParentClass", "Node", "Language"], "edit" => ["Class", "ParentClass", "Language"] ], "custom_module" => [ "custom_function_1" => null, "custom_function_2" => ["CustomLimitation"] ], ] ``` Limitations need to be implemented as *Limitation types* and declared as services identified with `ibexa.permissions.limitation_type` tag. Name provided in the hash for each limitation is the same value set in the `alias` attribute in the service tag. For example: ``` addConfig([ "custom_module" => [ "custom_function_1" => null, "custom_function_2" => ["CustomLimitation"], ], ]); } } ``` > **Note: Extend existing policies** > > While a `PolicyProvider` may provide new functions to an existing policy module, or additional limitations to an existing function, it's however strongly recommended to create your own modules. > > It's impossible to remove an existing module, function or limitation from a policy. ### YamlPolicyProvider An abstract class based on YAML is provided: `Ibexa\Bundle\Core\DependencyInjection\Security\PolicyProvider\YamlPolicyProvider`. It defines an abstract `getFiles()` method. Extend `YamlPolicyProvider` and implement `getFiles()` to return absolute paths to your YAML files. ``` addConfig([ "custom_module" => [ "custom_function_1" => null, "custom_function_2" => ["CustomLimitation"], ], ]); } public static function getTranslationMessages(): array { return [ (new Message('role.policy.custom_module', 'forms'))->setDesc('Custom module'), (new Message('role.policy.custom_module.all_functions', 'forms'))->setDesc('Custom module / All functions'), (new Message('role.policy.custom_module.custom_function_1', 'forms'))->setDesc('Custom module / Function #1'), (new Message('role.policy.custom_module.custom_function_2', 'forms'))->setDesc('Custom module / Function #2'), ]; } } ``` Then, extract this translation to generate the English translation file `translations/forms.en.xlf`: ``` php bin/console jms:translation:extract en --domain=forms --dir=src --output-dir=translations ``` ## `PolicyProvider` integration into `IbexaCoreBundle` For a `PolicyProvider` to be active, you have to register it in the `src/Kernel.php`: ``` getExtension('ibexa'); // Add the policy provider $ibexaExtension->addPolicyProvider(new MyPolicyProvider()); } } ``` ## Custom limitation type For a custom module function, you can use existing limitation types or create custom ones. The base of a custom limitation is a class to store values for the usage of this limitation in roles, and a class to implement the limitation's logic. The value class extends `Ibexa\Contracts\Core\Repository\Values\User\Limitation` and says for which limitation it's used: ``` limitationValues)) { $validationErrors[] = new ValidationError("limitationValues['value'] is missing."); } elseif (!is_bool($limitationValue->limitationValues['value'])) { $validationErrors[] = new ValidationError("limitationValues['value'] is not a boolean."); } return $validationErrors; } public function buildValue(array $limitationValues): CustomLimitationValue { $value = false; if (array_key_exists('value', $limitationValues)) { $value = $limitationValues['value']; } elseif (count($limitationValues)) { $value = (bool)$limitationValues[0]; } return new CustomLimitationValue(['limitationValues' => ['value' => $value]]); } /** * @param \Ibexa\Contracts\Core\Repository\Values\ValueObject[]|null $targets * * @return bool|null */ public function evaluate(Limitation $value, UserReference $currentUser, object $object, ?array $targets = null): ?bool { if (!$value instanceof CustomLimitationValue) { throw new InvalidArgumentException('$value', 'Must be of type: CustomLimitationValue'); } if ($value->limitationValues['value']) { return Type::ACCESS_GRANTED; } // If the limitation value is not set to `true`, then $currentUser, $object and/or $targets could be challenged to determine if the access is granted or not; Here or elsewhere. When passing the baton, a limitation can return Type::ACCESS_ABSTAIN return Type::ACCESS_DENIED; } public function getCriterion(Limitation $value, UserReference $currentUser): CriterionInterface { throw new NotImplementedException(__METHOD__); } public function valueSchema(): never { throw new NotImplementedException(__METHOD__); } } ``` The type class is set as a service tagged `ibexa.permissions.limitation_type` with an alias to identify it, and to link it to the value. ``` services: # … App\Security\Limitation\CustomLimitationType: tags: - { name: 'ibexa.permissions.limitation_type', alias: 'CustomLimitation' } ``` ### Custom limitation type form #### Form mapper To provide support for editing custom policies in the back office, you need to implement [`Ibexa\AdminUi\Limitation\LimitationFormMapperInterface`](https://github.com/ibexa/admin-ui/blob/5.0/src/lib/Limitation/LimitationFormMapperInterface.php). - `mapLimitationForm` adds the limitation field as a child to a provided Symfony form. - `getFormTemplate` returns the path to the template to use for rendering the limitation form. Here it use [`form_label`](https://symfony.com/doc/7.4/form/form_customization.html#reference-forms-twig-label) and [`form_widget`](https://symfony.com/doc/7.4/form/form_customization.html#reference-forms-twig-widget) to do so. - `filterLimitationValues` is triggered when the form is submitted and can manipulate the limitation values, such as normalizing them. ``` add('limitationValues', CheckboxType::class, [ 'label' => LimitationIdentifierToLabelConverter::convert($data->getIdentifier()), 'required' => false, 'data' => $data->limitationValues['value'], 'property_path' => 'limitationValues[value]', ]); } public function getFormTemplate(): string { return '@ibexadesign/limitation/custom_limitation_form.html.twig'; } public function filterLimitationValues(Limitation $limitation): void { } } ``` Provide a template corresponding to `getFormTemplate`. ``` {# templates/themes/admin/limitation/custom_limitation_form.html.twig #} {{ form_label(form.limitationValues) }} {{ form_widget(form.limitationValues) }} ``` Next, register the service with the `ibexa.admin_ui.limitation.mapper.form` tag and set the `limitationType` attribute to the limitation type's identifier: ``` App\Security\Limitation\Mapper\CustomLimitationFormMapper: tags: - { name: 'ibexa.admin_ui.limitation.mapper.form', limitationType: 'CustomLimitation' } ``` #### Notable form mappers to extend Some abstract limitation type form mapper classes are provided to help implementing common complex limitations. - `MultipleSelectionBasedMapper` is a mapper used to build forms for limitations based on a checkbox list, where multiple items can be chosen. For example, it's used to build forms for [Content Type Limitation](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#content-type-limitation), [Language Limitation](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#language-limitation) or [Section Limitation](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#section-limitation). - `UDWBasedMapper` is used to build a limitation form where a content/location must be selected. For example, it's used by the [Subtree Limitation](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#subtree-limitation) form. #### Value mapper By default, without a value mapper, the limitation value is rendered by using the block `ibexa_limitation_value_fallback` of the template [`vendor/ibexa/admin-ui/src/bundle/Resources/views/themes/admin/limitation/limitation_values.html.twig`](https://github.com/ibexa/admin-ui/blob/v5.0.7/src/bundle/Resources/views/themes/admin/limitation/limitation_values.html.twig). To customize the rendering, a value mapper eventually transforms the limitation value and sends it to a custom template. The value mapper implements [`Ibexa\AdminUi\Limitation\LimitationValueMapperInterface`](https://github.com/ibexa/admin-ui/blob/4.5/src/lib/Limitation/LimitationValueMapperInterface.php). Its `mapLimitationValue` function returns the limitation value transformed for the needs of the template. ``` */ public function mapLimitationValue(Limitation $limitation): array { return [$limitation->limitationValues['value']]; } } ``` Then register the service with the `ibexa.admin_ui.limitation.mapper.value` tag and set the `limitationType` attribute to limitation type's identifier: ``` App\Security\Limitation\Mapper\CustomLimitationValueMapper: tags: - { name: 'ibexa.admin_ui.limitation.mapper.value', limitationType: 'CustomLimitation' } ``` When a value mapper exists for a limitation, the rendering uses a Twig block named `ibexa_limitation__value` where `` is the limitation identifier in lower case. In this example, block name is `ibexa_limitation_customlimitation_value` as the identifier is `CustomLimitation`. This template receives a `values` variable which is the return value of the `mapLimitationValue` function from the corresponding value mapper. ``` {# templates/themes/standard/limitation/custom_limitation_value.html.twig #} {% block ibexa_limitation_customlimitation_value %} {% set is_set = values | first %} {{ is_set ? 'Yes' : 'No' }} {% endblock %} ``` To have your block found, you have to register its template. Add the template to the configuration under `ibexa.system..limitation_value_templates`: ``` ibexa: system: default: limitation_value_templates: - { template: '@ibexadesign/limitation/custom_limitation_value.html.twig', priority: 0 } ``` Provide translations for your custom limitation form in the `ibexa_content_forms_policies` domain. For example, `translations/ibexa_content_forms_policies.en.yaml`: ``` policy.limitation.identifier.customlimitation: 'Custom limitation' ``` ### Custom limitation check Check if current user has this custom limitation set to true from a custom controller: ``` getCustomLimitationValue()) { // Action only for user having the custom limitation checked } return new Response('...'); } private function getCustomLimitationValue(): bool { $hasAccess = $this->permissionResolver->hasAccess('custom_module', 'custom_function_2'); if (is_bool($hasAccess)) { return $hasAccess; } $customLimitationValues = $this->permissionChecker->getRestrictions( $hasAccess, CustomLimitationValue::class ); return $customLimitationValues['value'] ?? false; } #[\Override] public function performAccessCheck(): void { $this->traitPerformAccessCheck(); $this->denyAccessUnlessGranted(new Attribute('custom_module', 'custom_function_2')); } } ``` # Users # Users Users in Ibexa DXP refer to all kinds of user accounts: administrators, editors, managers, or shop customers. All such user accounts have the same underlying mechanism and enable you to control access to the application, both the back office and the website front, by using the [permission system](https://doc.ibexa.co/en/latest/permissions/permissions/index.md). ## Invite and manage users - [User management product guide](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/users/user_management_guide/): Find out what's user management and check what functions Ibexa DXP offers in this area to effectively manage the digital ecosystem. - [Inviting users](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/users/invitations/): Manage user invitations to create an account in the frontend or the back office. - [Register new users](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/users/user_registration/): Register new users. - [Update basic user data from CLI](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/users/update_basic_user_data/): Update basic user account data from the console. ## Authenticate users - [Login methods](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/users/login_methods/): Set up user login methods. - [Passwords](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/users/passwords/): Set up user password rules. - [User authentication](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/users/user_authentication/): Customize user authentication. - [OAuth client](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/users/oauth_client/): Allow users to log into Ibexa DXP through external OAuth2 authorization servers. - [OAuth Server](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/users/oauth_server/): Other applications can authenticate Ibexa DXP users through OAuth2 protocol then access to their resources on the platform. ## Group users - [Customer groups](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/users/customer_groups/): Assigning users to customer groups allows defining user-specific pricing rules. - [Segment API](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/users/segment_api/): You can use PHP API to get segment information, create and manage segments, and assign users to them. # User management product guide User management is a fundamental aspect of any system. Ibexa DXP offers a comprehensive and feature-rich user management system that allows organizations to efficiently manage their digital ecosystem. ## What is user management User management refers to the process of granting, configuring, and controlling access for users by administrators. This encompasses the creation of user accounts, assigning roles and permissions, setting authentication methods, and managing user-related data. ## Availability User management is available in all Ibexa DXP versions. ## How does user management work Ibexa DXP simplifies user management with an intuitive and powerful system of accounts, roles, permissions, groups, and segments. You can find all user groups and users in the **Admin** panel by selecting **Users**. Here, you can manage users, their relations, roles, and policies. *[Image: User's section]* Here's how it works: - User accounts - create and manage user accounts. This includes capturing user information, such as name, email, and profile details. - Roles and permissions - define roles and assign permissions to them. This ensures that users have appropriate access to content and functionalities. Roles can be customized to match the organization's specific needs. - Authentication methods - enable multiple authentication methods, including traditional username and password, OAuth, and external service logins. This flexibility allows organizations to adapt to various user authentication requirements. - User segmentation - segment users based on criteria such as demographics, behavior, or preferences. This segmentation enables personalized content delivery and targeted marketing. - Invitations - invite users to join a platform streamlining an onboarding process, sending invitations for exclusive content or events. - Customer groups - organize users into customer groups, which helps in delivering tailored experiences and content to specific segments. *[Image: User management]* ## Capabilities The detailed capabilities of Ibexa user management, which provide organizations with the tools they need to deliver personalized, secure, and efficient user experiences while ensuring that user access and content delivery align with their business goals and strategies. ### User roles and permissions Ibexa allows you to define custom user [roles with granular permissions](https://doc.ibexa.co/en/latest/permissions/permission_overview/index.md), ensuring that users have access to only the specific parts of the system they need. Furthermore, you can create user groups to simplify Permission management. Assign multiple users to a group to ensure consistency and ease of access control. This helps maintain effortless security and control. To help you understand further the role each element serves, here's a brief summary: - Role - represents a collection of Permissions that can be assigned to users or user groups. Roles streamline permission management by grouping related Permissions together. - Permission - defines a specific action or access level that can be granted or denied within the system. - Policy - is a set of rules or conditions that determine under what circumstances a specific permission is granted or denied by applying limitations. Policies allow for fine-grained control of access based on various factors, such as user attributes or system states. ### Custom policies [Tailor user access control](https://doc.ibexa.co/en/latest/permissions/custom_policies/index.md) to your unique requirements by using custom policies. Define complex rules and access criteria for different users or groups. ### Limitations [Implement limitations](https://doc.ibexa.co/en/latest/permissions/limitations/index.md) on user actions based on specific criteria, such as time-based restrictions or geographic locations. ### Authentication methods Ibexa offers flexibility in authentication methods to cater to different user bases and security requirements. *[Image: Log in via Google]* Available options: - [Username and password](https://doc.ibexa.co/en/latest/users/passwords/index.md) - ideal for most users, this traditional method offers a secure login process with username and password. - [OAuth client](https://doc.ibexa.co/en/latest/users/oauth_client/index.md) - integrating OAuth authentication allows users to log in using their existing social media credentials (like Google, Facebook, and Twitter), or the enterprise's system (like Active Directory or LDAP). - [OAuth server](https://doc.ibexa.co/en/latest/users/oauth_server/index.md) - client applications (such as mobile apps) can authenticate a user by using the platform's login screen, then access resources. ### Invitations The [invitation system](https://doc.ibexa.co/en/latest/users/invitations/index.md) streamlines user onboarding and engagement. Track the status of invitations, including when they were sent, whether they were accepted, and the actions taken by users who accepted them. *[Image: Invitations]* ### User segmentation and personalization Ibexa's segmentation and personalization features allow organizations to deliver customized user experiences. Track user behavior, such as page views, search queries, and interactions, to create segments and segment groups for users who share similar behaviors. *[Image: Segment groups]* Possible uses: - Demographics - segment users based on demographic data such as age, location, and gender to personalize content, promotions, and recommendations. - Behavior - tailor content based on user behavior, such as frequent content consumption, shopping patterns, or search history, ensuring users see what they're interested in. - Preferences - utilize user preferences to offer a customized experience, from language preferences to content type preferences. ### Customer groups The customer group functionality allows for targeted content delivery and service offerings. Set specific permissions for customer groups to control who can access and edit certain content to get respective recommendations. Possible uses: - Product recommendations - create customer groups based on product preferences and offer tailored product recommendations. - Content access control - restrict access to premium or specialized content to specific customer groups, such as paid subscribers or loyal customers. ## Benefits ### Improved user experience With role-based access control and personalized content, users have a more engaging and relevant experience on your platform. ### Enhanced security The flexible authentication methods and permission management help safeguard sensitive data and maintain security. With the ability to define and manage user roles and permissions, clients can ensure that sensitive data and actions are protected. User management helps prevent unauthorized access. ### Efficient user onboarding Invitations and account creation streamline the process of onboarding new users. ### Targeted marketing Customer groups and user segmentation capabilities allow for targeted and effective marketing campaigns. ### Content governance Clients can enforce content governance by controlling who can edit and publish content. This ensures quality and consistency in their digital properties. ### Content relevance By delivering content that resonates with different user segments, clients can increase user engagement and retention. ### Customizability Clients can adapt the user management system to their unique needs. Custom policies and limitations enable tailored solutions that align with their specific use cases. # Inviting users Ibexa DXP allows you to create and send invitations to create an account in the frontend as a customer, the back office as an employee, or the Corporate Portal as an organisation member. You can send invitations to individual users or in bulk. ## Roles and policies To invite other members to the site or the back office, a user needs to have the `User:Invite` permission added to their role. You can limit the ability to invite other members to specific user groups, such as Editors, or to the specific roles within the group, for example: Admin, Buyer. ## Creating and sending invitations Invitations are created with [InvitationService](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-User-Invitation-InvitationService.html), but sending them requires additional setup. Ibexa DXP provides you with `Ibexa\User\Invitation\MailSender` implementation of `InvitationSender` interface for sending invitations via email. If you want to send invitations through different channels, you need to create a custom setup. ## Invitation and registration form templates ### Semantic configuration To set up custom templates for invitation or registration forms, create a template file and inform the system, through configuration, when to use this template. You might also set a SiteAccess under `scope`, to which the new user is invited. If the SiteAccess isn't set, it falls back to the default `site` value. For example, use the following [configuration](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa: system: : user_invitation: hash_expiration_time: P7D templates: mail: "@@App/invitation/mail.html.twig" ``` Here, you can specify which template should be used for the invitation mail, and what should be the expiration time for the invitation link included in that mail. If a user doesn't click the invitation link sent to them in time, you can refresh the invitation. Refresh resets the time limit and changes the hash in the invitation link. You can find more registration related templates in [Register new users documentation](https://doc.ibexa.co/en/latest/users/user_registration/#other-user-management-templates). # Register new users You can allow your users to create accounts by employing the `/register` route. This route leads to a registration form that, when filled in, creates a new user content item in the repository. ## User types There are two user types defined: `users` and `customers`. `users` are back office users that are involved in creating the page such as editors, and `customers` are frontend users. To decide where the user should be registered to, you need to specify their user type under the `ibexa.system..user_type_identifier` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files). ``` ibexa: system: : user_registration: user_type_identifier: user ``` ## User groups By default, new users generated in this way are placed in the Guest accounts group. You can select a different default group in the following section of configuration: ``` ibexa: system: default: user_registration: group_remote_id: ``` ## Registration form field configuration To modify the registration form template, add or remove fields under the `allowed_field_definitions_identifiers` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa: system: : user_registration: user_type_identifier: user form: allowed_field_definitions_identifiers: - first_name - last_name - user_account ``` ## Other user management templates You can also modify form templates in the following way: **Changing user password:** ``` ibexa: system: : user_change_password: templates: form: ``` **Password recovery forms:** ``` ibexa.site_access.config..user_forgot_password.templates.form ibexa.site_access.config..user_forgot_password_success.templates.form ibexa.site_access.config..user_forgot_password_login.templates.form ibexa.site_access.config..user_forgot_password.templates.mail ``` **Resetting password:** ``` ibexa.site_access.config..user_reset_password.templates.form ibexa.site_access.config..user_reset_password.templates.invalid_link ibexa.site_access.config..user_reset_password.templates.success ``` **User settings:** ``` ibexa.site_access.config..user_settings.templates.list ibexa.site_access.config..user_settings.templates.update ``` **Changing registration form templates:** To change the registration form template, follow the instructions in [Invitation and registration form templates](https://doc.ibexa.co/en/latest/users/invitations/#invitation-and-registration-form-templates). # Update basic user data from CLI Multiple user management scenarios may result in having to update basic user account data, such as user status, the password, or email. Especially, you may need to revoke user access by disabling the account when offboarding an employee, or change the user's forgotten password. You can do it without accessing the Admin UI, by running a console command. You reference the user account by passing the user login. ## Disable or enable user account Disable the user account: ``` php bin/console ibexa:user:update-user --disable ``` For example: ``` php bin/console ibexa:user:update-user --disable johndoe ``` Enable the user account: ``` php bin/console ibexa:user:update-user --enable ``` ## Change password Change the password associated with the user account: ``` php bin/console ibexa:user:update-user --password ``` After you run the command, enter the new password when prompted. The command runs in silent mode and inputs are not echoed. For more information about changing and revoking passwords, for example, when a security breach occurs, see [Passwords](https://doc.ibexa.co/en/latest/users/passwords/#revoking-passwords). ## Change email address Change the email address associated with the user account: ``` php bin/console ibexa:user:update-user --email= ``` # Login methods Two login methods are available: with user name or with email. Providers for these two methods are `ibexa.security.user_provider.username` and `ibexa.security.user_provider.email`. You can configure which method is allowed under the `security` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` security: providers: ibexa: chain: providers: [ibexa_username, ibexa_email] ibexa_username: id: ibexa.security.user_provider.username ibexa_email: id: ibexa.security.user_provider.email firewalls: #... ibexa_front: # ... provider: ibexa ``` You can customize per user field whether the email address used as a login method must be unique or not. To check that all existing user accounts have unique emails, run the `ibexa:user:audit-database` command. It lists all user accounts with duplicate emails. > **Caution: Caution** > > Because logging in with email was not available until version v3.0, you can come across issues if you use the option on an existing database. > > This may happen if more than one account uses the same email address. Login through the user name is still available. > > To resolve the issues, run `ibexa:user:audit-database` and manually modify accounts that have duplicate emails. ## Login rules You can set the rules for allowed user names in the back office per user field. The rules are set by using regular expressions. For example, to ensure that user names can only contain lowercase letters, set `[a-z]+$` as **Username pattern**: *[Image: Setting a user name pattern]* To check that all existing user accounts have names that fit the current pattern, run the `ibexa:user:audit-database` command. It checks all user accounts in the database and lists those that don't fit the pattern. # Passwords ## Changing and recovering passwords The user may request to change their password, or may forget it and ask to have it reset. To change password, the user must have the `user/password` permission. When the user requests a reset of a forgotten password, an email is sent to them with a token. It allows them to create a new password. For information about how to create and configure the template, see [Add forgot password option](https://doc.ibexa.co/en/latest/templating/layout/add_forgot_password_option/index.md) The template for this email is located in `Resources/views/forgot_password/mail/forgot_user_password.html.twig` in `ibexa/user`. You can [customize it according to your needs](https://doc.ibexa.co/en/latest/templating/layout/add_login_form/#customize-login-form). The validity of the password recovery token can be set by using the `ibexa.system..security.token_interval_spec` parameter. By default, it's set to `PT1H` (one hour). ## Revoking passwords In case of a security situation such as a data leakage, you may need to force users to change their passwords. You can do it with the help of the `ibexa:user:expire-password` command, which revokes the passwords for specific users, user groups, or users belonging to the chosen content type. To select which users to revoke passwords for, use one of the following options with the command: - `--user-id|-u` - the ID of the user. Accepts multiple user IDs - `--user-group-id|-ug` - the ID of the user group. Accepts multiple group IDs - `--user-content-type-identifier|-ct` - the identifier of the user content type. Accepts multiple content types You can use the following additional options with the command: - `--force|-f` - commits the change, otherwise the command only performs a dry run - `--iteration-count|-c` - defines how many users are fetched at once. Lowering this value helps with memory issues - `--password-ttl|-t` - number of days after which new passwords expire. Used when the command enables password expiration for user content types that don't use it yet. For example, to revoke the passwords of all users of the `user` content type, run: ``` php bin/console ibexa:user:expire-password --user-content-type-identifier=user --force ``` To perform a dry run (without saving the results) of revoking passwords of all users from user group 13, run: ``` php bin/console ibexa:user:expire-password --user-group-id=13 ``` ## Password rules You can customize the password policy in your project. Each password setting is customizable per user field type. You can change the [password attributes](#password-attributes) or [password expiration settings](#password-expiration), and determine the rules for [repeating passwords](#repeating-passwords). To access the password settings: 1. In the back office, go to **Content** -> **Content types**. 2. In the **Content type groups** table, click **Users**. 3. Edit the **User** content type. 4. In the **Field definitions** list, view the settings for **User account (ibexa_user)**. > **Tip: Tip** > > There can be other content types that function as users, beyond the built-in user content type. For details, see [User Identifiers](https://doc.ibexa.co/en/latest/administration/configuration/repository_configuration/#user-identifiers). ## Password attributes In the **User account (ibexa_user)** Field definition, you can determine if the password must contain at least: - One uppercase letter - One lowercase letter - One number - One non-alphanumeric character You can also set the minimum password length. ## Password expiration In the **User account (ibexa_user)** field definition, you can set password expiration rules, which forces users to change their passwords periodically. *[Image: Password expiry settings]* You can also decide when the user is notified that they need to change their password. The notification is displayed in the back office after login and in the user content item's preview. ## Repeating passwords You can set a rule that the password cannot be reused. You set it for the user content type in the **User account (ibexa_user)** field type's settings. When this is set, the user cannot type in the same password when it expires. It has to be changed to a new one. This only checks the new password against the current one. A password that has been used before can be used again. This rule is valid by default when password expiration is set. ## Breached passwords You can set a rule that prevents using passwords which have been exposed in a public breach. To do this, in the **User account (ibexa_user)** field definition, select "Password must not be contained in a public breach". *[Image: Protection against using breached passwords]* This rule checks the password against known password dumps by using the API. It doesn't check existing passwords, so it doesn't block login for anyone. It applies only to new passwords when users change them. > **Note: Note** > > The password itself isn't sent to the API, which makes this check secure. > > For more information on how that is possible, see [Validating Leaked Passwords with k-Anonymity](https://blog.cloudflare.com/validating-leaked-passwords-with-k-anonymity/). # User authentication ## Authenticate user with multiple user providers Symfony provides native support for [multiple user providers](https://symfony.com/doc/7.4/security/user_providers.html). This makes it easier to integrate any kind of login handlers, including SSO and existing third party bundles (for example, [FR3DLdapBundle](https://github.com/Maks3w/FR3DLdapBundle), [HWIOauthBundle](https://github.com/hwi/HWIOAuthBundle), [FOSUserBundle](https://github.com/FriendsOfSymfony/FOSUserBundle), or [BeSimpleSsoAuthBundle](https://github.com/BeSimple/BeSimpleSsoAuthBundle)). However, to be able to use *external* user providers with Ibexa DXP, a valid Platform user needs to be injected into the repository. This is mainly for the kernel to be able to manage content-related permissions (but not limited to this). Depending on your context, you either want to create a Platform user, return an existing user, or even always use a generic user. Whenever an *external* user is matched (i.e. one that doesn't come from Platform repository, like coming from LDAP), Ibexa DXP kernel initiates an `MVCEvents::INTERACTIVE_LOGIN` event. Every service listening to this event receives an `Ibexa\Core\MVC\Symfony\Event\InteractiveLoginEvent` object which contains the original security token (that holds the matched user) and the request. Then, it's up to the listener to retrieve a Platform user from the repository and to assign it back to the event object. This user is injected into the repository and used for the rest of the request. If no Ibexa DXP user is returned, the Anonymous user is used. ### User exposed and security token When an *external* user is matched, a different token is injected into the security context, the `InteractiveLoginToken`. This token holds a `UserWrapped` instance which contains the originally matched user and the *API user* (the one from the Ibexa DXP repository). The *API user* is mainly used for permission checks against the repository and thus stays *under the hood*. ### Customize the user class It's possible to customize the user class used by extending `Ibexa\Core\MVC\Symfony\Security\EventListener\SecurityListener` service, which defaults to `Ibexa\Core\MVC\Symfony\Security\EventListener\SecurityListener`. You can override `getUser()` to return whatever user class you want, as long as it implements `Ibexa\Core\MVC\Symfony\Security\UserInterface`. The following is an example of using the in-memory user provider: ``` # config/packages/security.yaml security: providers: # Chaining in_memory and ibexa user providers chain_provider: chain: providers: [in_memory, ibexa] ibexa: id: ibexa.security.user_provider in_memory: memory: users: # You will then be able to login with username "user" and password "userpass" user: { password: userpass, roles: [ 'ROLE_USER' ] } # The "in memory" provider requires an encoder for Symfony\Component\Security\Core\User\User encoders: Symfony\Component\Security\Core\User\User: plaintext ``` ### Implement the listener In the `config/services.yaml` file: ``` services: App\EventListener\InteractiveLoginListener: arguments: ['@ibexa.api.service.user'] tags: - { name: kernel.event_subscriber }  ``` Don't mix `MVCEvents::INTERACTIVE_LOGIN` event (specific to Ibexa DXP) and `SecurityEvents::INTERACTIVE_LOGIN` event (fired by Symfony security component). ``` userService = $userService; } public static function getSubscribedEvents() { return [ MVCEvents::INTERACTIVE_LOGIN => 'onInteractiveLogin' ]; } public function onInteractiveLogin(InteractiveLoginEvent $event) { // This loads a generic User and assigns it back to the event. // You may want to create Users here, or even load predefined Users depending on your own rules. $event->setApiUser($this->userService->loadUserByLogin( 'lolautruche' )); } }  ``` # OAuth client You can use OAuth2 to securely authenticate users with external Authorization Servers. *[Image: OAuth2 Client]* Ibexa DXP uses an integration with [`knpuniversity/oauth2-client-bundle`](https://github.com/knpuniversity/oauth2-client-bundle) to provide OAuth2 authentication. ## Configure OAuth2 client ### Configure connection to Authorization Server Details of the configuration depend on the OAuth2 Authorization Server that you want to use. For sample configurations for different providers, see [`knpuniversity/oauth2-client-bundle` configuration](https://github.com/knpuniversity/oauth2-client-bundle#configuration). Some client types require additional packages. Missing package is indicated in an error message. For example, the following configuration creates a `google` client for Google OAuth2 Authorization Server to log users in. Two environment variables, `OAUTH_GOOGLE_CLIENT_ID` and `OAUTH_GOOGLE_CLIENT_SECRET`, correspond to [the set-up on Google side](https://support.google.com/cloud/answer/15549257). ``` knpu_oauth2_client: clients: # Configure your clients as described here: https://github.com/knpuniversity/oauth2-client-bundle#configuration google: type: google client_id: '%env(OAUTH_GOOGLE_CLIENT_ID)%' client_secret: '%env(OAUTH_GOOGLE_CLIENT_SECRET)%' redirect_route: ibexa.oauth2.check redirect_params: identifier: google ``` To use the `google` client type, you need to install the following package: ``` composer require league/oauth2-google ``` ### Enable OAuth2 client The client needs to be a part of the [SiteAccess scope](https://doc.ibexa.co/en/latest/multisite/multisite_configuration/#scope). In the following example, the OAuth2 client `google` is enabled for the `admin` SiteAccess: ``` ibexa: system: admin: oauth2: enabled: true clients: ['google'] ``` ## Configure firewall In `config/packages/security.yaml`, enable the `ibexa_oauth2_connect` firewall and replace the `ibexa_front` firewall with the `ibexa_oauth2_front` one. ``` security: #… firewalls: #… # Uncomment ibexa_oauth2_connect, ibexa_oauth2_front rules and comment ibexa_front firewall # to enable OAuth2 authentication ibexa_oauth2_connect: pattern: /oauth2/connect/* security: false ibexa_oauth2_front: pattern: ^/ provider: ibexa user_checker: Ibexa\Core\MVC\Symfony\Security\UserChecker custom_authenticators: - Ibexa\Bundle\OAuth2Client\Security\Authenticator\OAuth2Authenticator - Ibexa\PageBuilder\Security\EditorialMode\FragmentAuthenticator entry_point: Ibexa\Bundle\OAuth2Client\Security\Authenticator\OAuth2Authenticator context: ibexa form_login: enable_csrf: true logout: ~ # ibexa_front: # pattern: ^/ # provider: ibexa # user_checker: Ibexa\Core\MVC\Symfony\Security\UserChecker # context: ibexa # form_login: # enable_csrf: true # login_path: login # check_path: login_check # custom_authenticators: # - Ibexa\PageBuilder\Security\EditorialMode\FragmentAuthenticator # entry_point: form_login # logout: # path: logout ``` The `custom_authenticators` setting specifies the [custom authenticators](https://symfony.com/doc/7.4/current/security/custom_authenticator.html) to be used. By adding the `Ibexa\Bundle\OAuth2Client\Security\Authenticator\OAuth2Authenticator` authenticator you add a possibility to use OAuth2 on those routes. ## Resource owner mappers Resource owner mappers map the data received from the OAuth2 authorization server to user information in the repository. Resource owner mappers must implement the [`Ibexa\Contracts\OAuth2Client\ResourceOwner\ResourceOwnerMapper`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-OAuth2Client-ResourceOwner-ResourceOwnerMapper.html) interface. Four implementations of `ResourceOwnerMapper` are proposed by default: - `ResourceOwnerToExistingUserMapper` is the base class extended by the following mappers: - `ResourceOwnerIdToUserMapper` - loads a user (resource owner) based on the identifier, but doesn't create a new user. - `ResourceOwnerEmailToUserMapper` - loads a user (resource owner) based on the email, but doesn't create a new user. - `ResourceOwnerToExistingOrNewUserMapper` - checks whether the user exists and loads the data if it does. If not, creates a new user in the repository. To use `ResourceOwnerToExistingOrNewUserMapper`, you need to extend it in your custom mapper. > **Tip: OAuth user content type** > > When you implement your own mapper for external login, it's good practice to create a special user content type for users registered in this way. The users who register through an external service don't have a separate password in the system. Instead, they log in by their external service's password. > > To avoid issues with password restrictions in the built-in user content type, create a special content type (for example, "OAuth user"), without restrictions on the password. > > This new content type must also contain the user (`ibexa_user`) field. The following example shows how to create a Resource Owner mapper for the `google` client from previous examples. Create a resource owner mapper for Google login in `src/OAuth/GoogleResourceOwnerMapper.php`. The mapper extends `ResourceOwnerToExistingOrNewUserMapper`, which enables it to create a new user in the repository if the user doesn't exist yet. The mapper loads a user (line 40) or creates a new one (line 49), based on the information from `resourceOwner`, that's the OAuth2 authorization server. The new username is set with a `google:` prefix (lines 20, 91), to avoid conflicts with users registered in a regular way. ``` loadUserByIdentifier($this->getUsername($resourceOwner)); } /** * @param \League\OAuth2\Client\Provider\GoogleUser $resourceOwner */ protected function createUser( ResourceOwnerInterface $resourceOwner, UserProviderInterface $userProvider ): UserInterface { $userCreateStruct = $this->oauthUserService->newOAuth2UserCreateStruct( $this->getUsername($resourceOwner), $resourceOwner->getEmail(), $this->getMainLanguageCode(), $this->getOAuth2UserContentType($this->repository) ); $userCreateStruct->setField('first_name', $resourceOwner->getFirstName()); $userCreateStruct->setField('last_name', $resourceOwner->getLastName()); $parentGroups = []; if ($this->parentGroupRemoteId !== null) { $parentGroups[] = $this->userService->loadUserGroupByRemoteId($this->parentGroupRemoteId); } $this->userService->createUser($userCreateStruct, $parentGroups); return $userProvider->loadUserByIdentifier($this->getUsername($resourceOwner)); } private function getOAuth2UserContentType(Repository $repository): ?ContentType { if ($this->contentTypeIdentifier !== null) { $contentTypeService = $repository->getContentTypeService(); return $contentTypeService->loadContentTypeByIdentifier( $this->contentTypeIdentifier ); } return null; } private function getMainLanguageCode(): string { // Get first prioritized language for current scope return $this->languageResolver->getPrioritizedLanguages()[0]; } private function getUsername(GoogleUser $resourceOwner): string { return self::PROVIDER_PREFIX . $resourceOwner->getId(); } } ``` Configure the service by using the `ibexa.oauth2_client.resource_owner_mapper` tag to associate it with the `google` client: ``` services: #… App\OAuth\GoogleResourceOwnerMapper: tags: - { name: ibexa.oauth2_client.resource_owner_mapper, identifier: google } ``` ## Add login button After you have activated the OAuth2 client for the `admin` SiteAccess, you need to add a **Log in with Google** to the back office login form. Create the following template file in `templates/themes/admin/account/login/oauth2_login.html.twig`: ``` ``` For more information about the OAuth connection URL Twig functions, see [`ibexa_oauth2_connect_path`](https://doc.ibexa.co/en/latest/templating/twig_function_reference/url_twig_functions/#ibexa_oauth2_connect_path) and [`ibexa_oauth2_connect_url`](https://doc.ibexa.co/en/latest/templating/twig_function_reference/url_twig_functions/#ibexa_oauth2_connect_url). Finally, add the template to the login form by using the `admin-ui-login-form-after` [Twig component group](https://doc.ibexa.co/en/latest/templating/components/index.md): ``` services: #… app.components.oauth2_login: parent: Ibexa\TwigComponents\Component\TemplateComponent arguments: $template: '@@ibexadesign/account/login/oauth2_login.html.twig' tags: - { name: ibexa.twig.component, group: admin-ui-login-form-after } ``` *[Image: Log in to the back office with Google]* # OAuth Server Your Ibexa DXP instance can be used as an OAuth2 server, combining the roles of an Authorization Server and a Resource Server. Client applications, such as mobile apps, are able to authenticate a user, and then access this user's resources. *[Image: OAuth2 Server]* ## Server installation Ibexa DXP Oauth2 server package is `ibexa/oauth2-server` and isn't part of the default installation. You can install it with the following command: ``` composer require ibexa/oauth2-server --with-all-dependencies ``` Add the tables needed by the bundle: **MySQL** ``` php bin/console ibexa:doctrine:schema:dump-sql vendor/ibexa/oauth2-server/src/bundle/Resources/config/schema.yaml | mysql -u -p ``` **PostgreSQL** ``` php bin/console ibexa:doctrine:schema:dump-sql --force-platform=postgres vendor/ibexa/oauth2-server/src/bundle/Resources/config/schema.yaml | psql ``` Then, in `config/bundles.php`, at the end of an array with a list of bundles, add the following two lines : ``` ['all' => true], League\Bundle\OAuth2ServerBundle\LeagueOAuth2ServerBundle::class => ['all' => true], ]; ``` ## Authorization Server configuration ### Keys To configure the server, you need private and public keys. For more information, see [Generating public and private keys](https://oauth2.thephpleague.com/installation/#generating-public-and-private-keys). You also need an encryption key. For more information, see [Generating encryption keys](https://oauth2.thephpleague.com/installation/#generating-encryption-keys). Set the following environment variables: ``` OAUTH2_PUBLIC_KEY_PATH=/somewhere/safe/key.public OAUTH2_PRIVATE_KEY_PATH=/somewhere/safe/key.private OAUTH2_PRIVATE_KEY_PASSPHRASE=some_passphrase_or_empty OAUTH2_ENCRYPTION_KEY=1234567890123456789012345678901234567890 ``` ### Service, routes, and security configurations Uncomment the whole service configuration file: `config/packages/ibexa_oauth2_server.yaml`. Tweak the values if necessary. Uncomment the whole routes configuration file: `config/routes/ibexa_oauth2_server.yaml`. In `config/packages/security.yaml`, uncomment the following three lines under the `access_control` key: ``` security: #… # Uncomment authorize access control if you wish to use product as an OAuth2 Server access_control: - { path: ^/authorize/jwks$, roles: ~ } - { path: ^/authorize, roles: IS_AUTHENTICATED_REMEMBERED } ``` In `config/packages/security.yaml`, uncomment the three following lines under the `oauth2_token` key: ``` security: #… firewall: #… # Uncomment oauth2_token firewall if you wish to use product as an OAuth2 Server. # Use oauth2 guard any other (for example ibexa_front) firewall you wish to be # exposed as OAuth2-available resource. Example: # guard: # authenticators: # - Ibexa\OAuth2Server\Security\Guard\OAuth2Authenticator oauth2_token: pattern: ^/token$ security: false ``` ## Resource Server configuration To allow resource routes to be accessible through OAuth authorization, enable OAuth2 integration for the `ibexa_rest` firewall by setting the `oauth2` property to true. ## Client ### Add a client You need the client redirect URIs to create a client. You also need to agree on an identifier and a secret with the client. There is only one `default` [scope](https://oauth.net/2/scope/). Use `league:oauth2-server:create-client` command to create a client. For example: ``` php bin/console league:oauth2-server:create-client 'Example OAuth2 Client' example-oauth2-client 9876543210987654321098765432109876543210 --scope=default \ --redirect-uri=https://example.com/oauth2-callback ``` > **Note: Note** > > You can call `--redirect-uri` multiple times. Alternatively, you could add redirect URIs after you create a client, by using the `league:oauth2-server:update-client` command. For example: ``` php bin/console league:oauth2-server:update-client example-oauth2-client \ --add-redirect-uri=https://example.com/another-oauth2-callback ``` Other commands let you list all the configured clients (`league:oauth2-server:list-clients`) or delete a client (`league:oauth2-server:delete-client`). > **Note: Note** > > For a list of all the commands that you can use maintain your clients, in a terminal, run `bin/console list league:oauth2-server`. To see usage details for each of the commands, run `bin/console help ` . > > For more information, see the package's [online documentation](https://github.com/thephpleague/oauth2-server-bundle/blob/master/docs/basic-setup.md). ### Information needed by the client Your OAuth2 client needs the following information to be able to use your OAuth server: - The URL of the Ibexa DXP used as an OAuth server - The client identifier - The client secret - The scope (`default`) # Customer groups You can assign users to different customer groups to enable [custom pricing](https://doc.ibexa.co/en/latest/product_catalog/prices/index.md). This enables you to give specific prices or price discounts (global or per product) to specific groups of users. For example, you can offer a 10% discount for all products in the catalog to users who belong to the Resellers customer group. By using [Discounts](https://doc.ibexa.co/en/latest/discounts/discounts_guide/index.md), you can create even more complex rules that apply to only selected customer groups or to all customers. > **Tip: Tip** > > Customer groups aren't the same as [user groups](https://doc.ibexa.co/en/latest/users/user_registration/#user-groups). User groups concern all users in the system and can be used, for example, to handle permissions. Customer groups refer specifically to the commerce functionalities and enable handling prices. ## Enabling customer groups To enable the use of customer groups, you need to modify the user content type's definition by adding a [customer group field](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/customergroupfield/index.md). With this field you can add a user to any of the predefined customer groups. # Segment API Editions: Experience Segments enable you to profile the content displayed to specific users. To manage segments, use the `SegmentationService`. ## Getting segment information To load a segment group, use `SegmentationService::loadSegmentGroupByIdentifier()`. Get all segments assigned to the group with `SegmentationService::loadSegmentsAssignedToGroup()`: ``` $segmentGroup = $this->segmentationService->loadSegmentGroupByIdentifier('custom_group'); $segments = $this->segmentationService->loadSegmentsAssignedToGroup($segmentGroup); foreach ($segments as $segment) { $output->writeln('Segment identifier: ' . $segment->getIdentifier() . ', name: ' . $segment->getName()); } ``` Similarly, you can load a segment by using `SegmentationService::loadSegmentByIdentifier()`: ``` $segment = $this->segmentationService->loadSegmentByIdentifier('segment_1'); ``` ## Checking assignment You can check whether a user is assigned to a segment with `SegmentationService::isUserAssignedToSegment()`: ``` $output->writeln(( $this->segmentationService->isUserAssignedToSegment($user, $segment) ? 'The user is assigned to the segment.' : 'The user is not assigned to the segment.' )); ``` ## Assigning users To assign a user to a segment, use `SegmentationService::assignUserToSegment()`: ``` $this->segmentationService->assignUserToSegment($user, $segment); ``` ## Creating segments Each segment must be assigned to a segment group. To create a segment group, use `SegmentationService::createSegmentGroup()` and provide it with a `SegmentGroupCreateStruct`: ``` $segmentGroupCreateStruct = new SegmentGroupCreateStruct([ 'name' => 'Custom Group', 'identifier' => 'custom_group', 'createSegments' => [], ]); $newSegmentGroup = $this->segmentationService->createSegmentGroup($segmentGroupCreateStruct); ``` To add a segment, use `SegmentationService::createSegment()` and provide it with a `SegmentCreateStruct`, which takes an existing group as one of the parameters: ``` $segmentCreateStruct = new SegmentCreateStruct([ 'name' => 'Segment 1', 'identifier' => 'segment_1', 'group' => $newSegmentGroup, ]); $newSegment = $this->segmentationService->createSegment($segmentCreateStruct); ``` ## Updating segments To update a segment or a segment group, use `SegmentationService::updateSegment()` or `SegmentationService::updateSegmentGroup()` and provide it with `SegmentUpdateStruct` or `SegmentGroupUpdateStruct`. ## Deleting segments To delete a segment or a segment group, use `SegmentationService::removeSegment()` or `SegmentationService::removeSegmentGroup()`: ``` $this->segmentationService->removeSegmentGroup($group); ``` # Recommendations # Raptor connector The [Raptor](https://www.raptorservices.com/) connector is an add-on that provides a seamless integration between Ibexa DXP and Raptor recommendation engine. Its primary goal is to enable editors and managers to deliver personalized experiences across digital channels, which helps increase conversion rates, drive sales, and improve user engagement. By combining content management capabilities with advanced recommendation features, the connector allows teams to build and manage personalized experiences across integrated tools. The connector ensures a smooth and unified integration layer, enabling: - event tracking through the tracking API - personalized delivery of content and product recommendations through the Recommendations API - flexible, SiteAccess-aware configuration This approach reduces integration complexity while providing a scalable foundation for personalization use cases across multiple sites and markets. To configure the integration with Raptor, follow a step-by-step procedure that allows you to activate the Raptor connector. Activation includes [configuration](https://doc.ibexa.co/en/latest/recommendations/raptor_integration/connector_installation_configuration/index.md), adding tracking scripts and events, and using [Page Builder](https://doc.ibexa.co/en/latest/content_management/pages/page_builder_guide/index.md) blocks. For more information about tracking, check the Raptor documentation: [Implementing tracking](https://content.raptorservices.com/help-center/data-management#implementing-tracking). - [Configuration procedure](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/recommendations/raptor_integration/connector_installation_configuration/): To configure the Raptor integration, follow the step-by-step procedure described below. - [Raptor tracking functions](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/recommendations/raptor_integration/tracking_functions/): Integrate the tracking script to collect user interactions. - [Tracking with PHP API](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/recommendations/raptor_integration/tracking_php_api/): Tracking with PHP API. - [Recommendation blocks in Page Builder](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/recommendations/raptor_integration/recommendation_blocks/): Recommendation blocks in Page Builder - [Custom recommendation rendering](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/recommendations/raptor_integration/custom_recommendation_rendering/): Use existing controllers to render recommendations outside the Page Builder. - [Recommendations Twig functions](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/templating/twig_function_reference/recommendations_twig_functions/): Recommendations Twig Functions # Raptor integration guide Discover [Raptor](https://www.raptorservices.com/) integration - an add-on that is focused on recommendations and tracking customer behaviors. It includes Raptor connector with tracking scripts and events, used to track and analyze customer behaviors, and a set of Recommendation blocks. ## What is Raptor connector The [Raptor connector](https://doc.ibexa.co/en/latest/recommendations/raptor_integration/raptor_connector/index.md) provides a seamless integration between Ibexa DXP and Raptor recommendation engine. Its primary goal is to enable editors and managers to deliver personalized experiences across digital channels, which helps to increase conversion rates, drive sales, and improve user engagement. By bringing content and recommendations together, the connector makes it easy to build and manage personalized experiences. It provides a seamless integration layer that supports: - event tracking of user interactions through the Tracking API - personalized delivery of content and product recommendations through the Recommendations API - flexible SiteAccess-aware configuration adapted to different sites and contexts This approach simplifies integration while supporting personalization across different sites and markets. ## Availability Raptor integration elements, such as tracking, Twig functions, and public API, are available in all supported Ibexa DXP editions starting from v5.0.7 version. Recommendation blocks provided in Page Builder, are available in Ibexa Experience and Ibexa Commerce editions. ## How does Raptor tracking work To start [tracking](https://content.raptorservices.com/help-center/introduction-to-tracking-documentation) user interactions, the tracking script needs to be added to the website’s layout. Tracking can be set up either on the client-side or server-side, depending on how you want to capture and process the events. The tracking works differently depending on the mode you choose. In server-side mode, tracking happens on the server, handling all events without loading scripts in the browser. In client-side mode, it inserts script tags so tracking runs directly in the browser. You can switch between server and client tracking at any time by changing the tracking type to fit your setup and needs. ## Capabilities ### Tracking Raptor [tracking functions](https://doc.ibexa.co/en/latest/recommendations/raptor_integration/tracking_functions/index.md) allow you to collect data about how users interact with your products and content. You can track product visits to better understand what users are viewing. Provided [Twig functions](https://doc.ibexa.co/en/latest/templating/twig_function_reference/recommendations_twig_functions/index.md) simplify the implementation, allowing developers to quickly add tracking to templates without complex setup. This gives you the data you need to better understand user behavior, improve recommendations, and support personalization. ### Recommendation blocks The Raptor Integration add-on provides a set of ready-to-use recommendation blocks that can be added directly in the [Page Builder](https://doc.ibexa.co/en/latest/content_management/pages/page_builder_guide/index.md). These blocks can be configured to adjust how they work and what they display. Content, Product, and Commerce recommendations can be placed on landing pages using these components. Editors can use these blocks to display tailored product recommendations, promote related content, and highlight items that are trending or recently viewed. Recommendation blocks are organized into dedicated categories, each grouping blocks based on the type of recommendation they provide: - **Recommendations: Content** - presents content recommendations: - [Content that has been seen along with the item category](https://doc.ibexa.co/projects/userguide/en/5.0/personalization/raptor_integration/raptor_recommendation_blocks/#content-that-have-been-seen-along-with-the-item-category-block) - [Most popular content](https://doc.ibexa.co/projects/userguide/en/5.0/personalization/raptor_integration/raptor_recommendation_blocks/#most-popular-content-block) - [Other customers have also seen this content](https://doc.ibexa.co/projects/userguide/en/5.0/personalization/raptor_integration/raptor_recommendation_blocks/#other-customers-have-also-seen-this-content-block) - [Personalized content recommendations](https://doc.ibexa.co/projects/userguide/en/5.0/personalization/raptor_integration/raptor_recommendation_blocks/#personalized-content-recommendations-block) - **Recommendations: Product** - displays product suggestions based on visitors’ browsing history: - [Most popular products](https://doc.ibexa.co/projects/userguide/en/5.0/personalization/raptor_integration/raptor_recommendation_blocks/#most-popular-products-block) - [Most popular products in category](https://doc.ibexa.co/projects/userguide/en/5.0/personalization/raptor_integration/raptor_recommendation_blocks/#most-popular-products-in-category-block) - [Other customers have also seen](https://doc.ibexa.co/projects/userguide/en/5.0/personalization/raptor_integration/raptor_recommendation_blocks/#other-customers-have-also-seen-block) - **Recommendations: Commerce** - shows recommendations based on visitors' purchase history (buy and basket events): - [Other customers have also purchased block](https://doc.ibexa.co/projects/userguide/en/5.0/personalization/raptor_integration/raptor_recommendation_blocks/#other-customers-have-also-purchased-block) - [The Personal Shopping Assistant](https://doc.ibexa.co/projects/userguide/en/5.0/personalization/raptor_integration/raptor_recommendation_blocks/#the-personal-shopping-assistant-block) - [User's item history](https://doc.ibexa.co/projects/userguide/en/5.0/personalization/raptor_integration/raptor_recommendation_blocks/#users-item-history-block) *[Image: Recommendation blocks]* For a complete description of Recommendation blocks see [Recommendation blocks in User Documentation](https://doc.ibexa.co/projects/userguide/en/5.0/personalization/raptor_integration/raptor_recommendation_blocks/). ### Advanced usage for complex tracking scenarios For more complex tracking requirements, [PHP API](https://doc.ibexa.co/en/latest/recommendations/raptor_integration/tracking_php_api/index.md) provides direct access to the service. It lets you track custom user actions, create more detailed tracking logic, and support scenarios not covered by the standard client- or server-side setups. ## Benefits ### Understand user behavior Thanks to tracking functions, you can capture how users interact with your products and content, giving you valuable insights into their behavior. It helps you make data-driven decisions to improve engagement and personalize the user experience. ### Highlight recommendations Use recommendation blocks on your websites to highlight content targeted at your customers. Deliver relevant content and build trust in your brand. ### Suggest content to boost retention Help users find content of their interest quicker. Showing visitors content and products that match their interests helps keep them engaged and encourages them to come back. ### Meet customer expectations and increase engagement Recommendation blocks highlight products that match customers' interests. Tailored content boosts engagement by showing visitors information and products that align with their interests and fulfill their needs. This strengthens the connection between your brand and your audience, encouraging them to spend more time on your site and return more often. ### Increase average order value Use tracking for predictive analysis and find out what motivates users to put extra items into their carts. Start building predictions of their behaviors and suggest products your visitors are willing to buy. ### Track performance and increase conversions Use the Raptor service in your Commerce shop and see how recommendations drive sales. Keep track of which recommendations are shown to visitors and measure conversion rates to evaluate their effectiveness against your goals. ### Flexible tracking with PHP API Tracking using PHP API gives you full control over how events are recorded and processed. You can use it for complex scenarios, so tracking can be adapted to your specific needs or business requirements. # Configuration procedure To configure the [Raptor](https://www.raptorservices.com/) integration add-on, follow the step-by-step procedure below. ## Install Raptor connector Before you can proceed to configuring the integration with Raptor, install the Raptor connector. To do it, run the following command: ``` composer require ibexa/connector-raptor ``` > **Note: Note** > > The [Ibexa Messenger](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/background_tasks/index.md) package is installed automatically as a dependency, but must be configured to enable server-side tracking. See the Ibexa Messenger documentation for [configuration](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/background_tasks/#configure-package) details. ## SiteAccess-aware configuration To configure the Raptor connector, use the `ibexa.system..connector_raptor` configuration key in the `config/packages/ibexa_connector_raptor.yaml` file: ``` ibexa: system: : connector_raptor: enabled: true customer_id: ~ # Required tracking_type: client # One of: "client" or "server" # Raptor Recommendations API key recommendations_api_key: ~ # Required # Raptor Recommendations API URL, optional, set by default recommendations_api_url: '%ibexa.connector.raptor.recommendations.api_url%' ``` - `enabled` - enables or disables the connector for a given scope. Default value: `true`. If set to `false`, no tracking or recommendation requests are executed. - `customer_id` - an identifier used to authenticate requests to the recommendation engine. You can find this value as ["Account number"](https://doc.ibexa.co/en/latest/recommendations/raptor_integration/connector_installation_configuration/#customer-id) in Raptor Control Panel. - `tracking_type` - defines how user events are sent to the tracking API. Default value: `client`. Possible values: - `client` - tracking is executed in the browser using JavaScript snippets generated by the [Twig functions](https://doc.ibexa.co/en/latest/templating/twig_function_reference/recommendations_twig_functions/index.md) and included in the templates. This approach may be blocked by ad blockers. - `server` - tracking is handled on the backend, with events sent directly to the tracking API. It's not affected by ad blockers. - `recommendations_api_key` - an API key used to authenticate requests to the Recommendations API. This key allows the connector to retrieve personalized recommendations from the recommendation engine. You can find this value as ["API key"](https://doc.ibexa.co/en/latest/recommendations/raptor_integration/connector_installation_configuration/#recommendations-api-key) in Raptor Control Panel. - `recommendations_api_url` (optional) - overrides the default Raptor address, do not set it unless a custom endpoint is required. By default, `tracking_type` is set to `client` as client-side tracking is the standard Raptor mode. To understand the differences between client and server tracking types, including their advantages and disadvantages, refer to the [Raptor documentation](https://content.raptorservices.com/help-center/client-side-vs.-server-side-tracking). > **Note: Note** > > Only one tracking mode can be enabled at a time. Client-side and server-side tracking cannot be used together. ### Customer ID To find the value for the `customer_id` identifier, log in to Raptor Control Panel, and look for "Account number": A. In the top-left corner, above the account name, you can find the account number for the currently active account. B. Click the arrow icon in the top-left corner to expand the window. There you can see a list of all your accounts, with their numbers shown in the “Account number” column on the right. This way, if you have multiple accounts, you can locate and copy the number of any of your accounts, not just the active one. *[Image: Account number]* ### Recommendations API key To find the value for the `recommendations_api_key`, log in to Raptor Control Panel, and look for "API key". To do it, in the left panel, open the **Recommendations** section, and select **Website**. Next, click on the Web module you’re interested in. In the top-right corner, click the three-dot icon and select **API information**. A new window appears, where you can find the "API key" value. Click **Show API information** and copy the value. *[Image: API key]* ## Global configuration (non-SiteAccess-aware) The following settings are global and apply to the entire application (they are not scoped per SiteAccess): - `strict_exceptions` – when enabled, tracking exceptions are thrown instead of being silently handled. Default value: `%kernel.debug%`. This value can be overridden in `config/packages/ibexa_connector_raptor.yaml` file, for example: ``` ibexa: system: : connector_raptor: enabled: true customer_id: ~ # Required tracking_type: client # One of: "client" or "server" # Raptor Recommendations API key recommendations_api_key: ~ # Required # Raptor Recommendations API URL, optional, set by default recommendations_api_url: '%ibexa.connector.raptor.recommendations.api_url%' ibexa_connector_raptor: # When enabled, tracking exceptions are thrown instead of being silently handled strict_exceptions: true ``` # Raptor tracking functions [Raptor connector](https://doc.ibexa.co/en/latest/recommendations/raptor_integration/raptor_connector/index.md) introduces [visit tracking functionality](https://content.raptorservices.com/help-center/introduction-to-tracking-documentation) for collecting user interactions with products and content. The implementation includes product visit tracking with mapping to tracking parameters, and Twig functions for straightforward integration. Raptor integration introduces two Twig functions: - [`ibexa_tracking_script()`](https://doc.ibexa.co/en/latest/templating/twig_function_reference/recommendations_twig_functions/#ibexa_tracking_script-function) - allows you to embed main tracking script into the website. - [`ibexa_tracking_track_event()`](https://doc.ibexa.co/en/latest/templating/twig_function_reference/recommendations_twig_functions/#ibexa_tracking_track_event-function) - is responsible for sending event data to the service, enabling tracking of user interactions and behaviors. ## Embed tracking script You must embed the tracking script into the website’s layout to enable tracking. To do it, add the `ibexa_tracking_script()` Twig function into the section of your base layout template, for example, `@ibexadesign/pagelayout.html.twig`: ``` {# templates/pagelayout.html.twig #} {# ... other head content ... #} {# Initialize Raptor tracking - must be called before any tracking events #} {{ ibexa_tracking_script() }} {# ... page content ... #} ``` ## Tracking modes Tracking user interactions can be implemented on the client-side or the server-side. Each approach differs in where events are captured and how they are sent to the tracking backend. The [tracking Twig function](#embed-tracking-script) outputs different content depending on the mode: ``` # Server-side tracking connector_raptor: tracking_type: 'server' # Returns nothing (prod) or HTML comments (dev) # Client-side tracking connector_raptor: tracking_type: 'client' # Returns ``` ### Example custom integration Example custom integration with [TermsFeed](https://www.termsfeed.com/): ```` ``` html ```` ``` ``` # Tracking with PHP API You can interact directly with the [Raptor connector](https://doc.ibexa.co/en/latest/recommendations/raptor_integration/raptor_connector/index.md)'s service using the [PHP API](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/namespaces/ibexa-contracts-connectorraptor-tracking.html) for advanced tracking usage. ## Advanced usage – direct interaction with the service The [`ServerSideTrackingDispatcherInterface::dispatch()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorRaptor-Tracking-ServerSideTrackingDispatcherInterface.html#method_dispatch) method allows to send tracking data from the server side. It can be used in controllers, event subscribers, or any other part of the application. This method receives an [`EventDataInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorRaptor-Tracking-Event-EventDataInterface.html). For more information, see the available events in the [tracking event namespace](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/namespaces/ibexa-contracts-connectorraptor-tracking-event.html). ### Mapping event data The recommended method is [`EventMapperInterface::map()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorRaptor-Tracking-EventMapperInterface.html#method_map). This method receives an [`EventType`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorRaptor-Tracking-EventType.html#cases) case, a data depending on the event type (a [`ProductInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-ProductInterface.html), a [`Content`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Content.html), or a `string`), and a context's associative array that uses [`EventContext`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorRaptor-Tracking-EventContext.html) constants as keys. For more information, see the same arguments of the Twig function [`ibexa_tracking_track_event`](https://doc.ibexa.co/en/latest/templating/twig_function_reference/recommendations_twig_functions/#ibexa_tracking_track_event-function). | Event type | Data class | Context keys | | -------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `EventType::VISIT` | `ProductInterface` | (optional) `EventContext::CATEGORY_IDENTIFIER`, (optional) `EventContext::WEBSITE_ID` | | `EventType::CONTENT_VISIT` | `Content` | (optional) `EventContext::WEBSITE_ID` | | `EventType::BUY` | `ProductInterface` | `EventContext::SUBTOTAL`, `EventContext::CURRENCY`, `EventContext::QUANTITY`, (optional) `EventContext::CATEGORY_IDENTIFIER`, (optional) `EventContext::WEBSITE_ID` | | `EventType::BASKET` | `ProductInterface` | `EventContext::BASKET_CONTENT`, `EventContext::BASKET_ID`, (optional) `EventContext::CATEGORY_IDENTIFIER`, (optional) `EventContext::QUANTITY`, (optional) `EventContext::WEBSITE_ID` | | `EventType::ITEM_CLICK` | `string` (product code) | `EventContext::MODULE_NAME`, `EventContext::REDIRECT_URL` | Check the following example: ``` use Ibexa\Contracts\ConnectorRaptor\Tracking\EventContext; use Ibexa\Contracts\ConnectorRaptor\Tracking\EventMapperInterface; use Ibexa\Contracts\ConnectorRaptor\Tracking\EventType; use Ibexa\Contracts\ConnectorRaptor\Tracking\ServerSideTrackingDispatcherInterface; //… // Map product to VisitEventData automatically, override its category $eventData = $this->eventMapper->map(EventType::VISIT, $product, [ EventContext::CATEGORY_IDENTIFIER => 'electronics', ]); // Send tracking event $this->trackingDispatcher->dispatch($eventData); ``` ### Manual `EventData` creation Manual creation of EventData allows precise control over the events sent to the service. It enables you to define custom event parameters, track specific user interactions, and tailor data collection to advanced use cases. Check the following example: ``` use Ibexa\Contracts\ConnectorRaptor\Tracking\Event\VisitEventData; use Ibexa\Contracts\ConnectorRaptor\Tracking\ServerSideTrackingDispatcherInterface; // … $eventData = new VisitEventData( productCode: $product->getCode(), productName: $product->getName(), categoryPath: '25#Electronics;26#Smartphones', // Build manually currency: 'USD', itemPrice: '999.99' ); $this->trackingDispatcher->dispatch($eventData); ``` `categoryPath` parameter sets the category path for recommendations and needs to be composed manually following the specified format and rules: - format: `CategoryId#CategoryName;CategoryId#CategoryName`, for example, `25#Electronics;26#Smartphones` - if `CategoryName` is missing, repeat the ID, for example, `25#25;26#26` - if `CategoryId` is missing, use the `CategoryName`, for example, `Electronics;Smartphones` For more information, see the available events in the [tracking event namespace](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/namespaces/ibexa-contracts-connectorraptor-tracking-event.html). ### Example - event subscriber If you need to track [events](https://doc.ibexa.co/en/latest/api/event_reference/event_reference/index.md) automatically based on application events, you can use an event subscriber. It reacts to specific events in the application and triggers tracking logic without the need to add it manually in templates. ``` ['onResponse', -10]]; } public function onResponse(ResponseEvent $event): void { if (!$event->isMainRequest()) { return; } $request = $event->getRequest(); // Example: track only if request has specific attribute $product = $request->attributes->get('product'); if (null === $product) { return; } $eventData = $this->eventMapper->map(EventType::VISIT, $product); $this->trackingDispatcher->dispatch($eventData); } } ``` You can also use Ibexa DXP events, for example `CreateOrderEvent` from [Order management events](https://doc.ibexa.co/en/latest/api/event_reference/order_management_events/index.md). For more information, see [Event reference](https://doc.ibexa.co/en/latest/api/event_reference/event_reference/index.md). # Recommendation blocks in Page Builder Editions: Experience One of the Raptor Integration elements is the introduction of recommendation blocks available in the [Page Builder](https://doc.ibexa.co/en/latest/content_management/pages/page_builder_guide/index.md). Content, Product, and Commerce recommendations can be added to a landing page using the blocks. Editors can configure these blocks to display: - personalized product recommendations - related articles or content - recently viewed or popular items In the toolbar, corresponding categories for recommendation blocks are available, containing sets of blocks depending on the recommendation type: - **Recommendations: Content** - presents content recommendations: - [Content that has been seen along with the item category](https://doc.ibexa.co/projects/userguide/en/5.0/personalization/raptor_integration/raptor_recommendation_blocks/#content-that-has-been-seen-along-with-the-item-category-block) - [Most popular content](https://doc.ibexa.co/projects/userguide/en/5.0/personalization/raptor_integration/raptor_recommendation_blocks/#most-popular-content-block) - [Other customers have also seen this content](https://doc.ibexa.co/projects/userguide/en/5.0/personalization/raptor_integration/raptor_recommendation_blocks/#other-customers-have-also-seen-this-content-block) - [Personalized content recommendations](https://doc.ibexa.co/projects/userguide/en/5.0/personalization/raptor_integration/raptor_recommendation_blocks/#personalized-content-recommendations-block) - **Recommendations: Product** - displays product suggestions based on visitors’ browsing history: - [Most popular products](https://doc.ibexa.co/projects/userguide/en/5.0/personalization/raptor_integration/raptor_recommendation_blocks/#most-popular-products-block) - [Most popular products in category](https://doc.ibexa.co/projects/userguide/en/5.0/personalization/raptor_integration/raptor_recommendation_blocks/#most-popular-products-in-category-block) - [Other customers have also seen](https://doc.ibexa.co/projects/userguide/en/5.0/personalization/raptor_integration/raptor_recommendation_blocks/#other-customers-have-also-seen-block) - **Recommendations: Commerce** - shows recommendations based on visitors' purchase history (buy and basket events): - [Other customers have also purchased block](https://doc.ibexa.co/projects/userguide/en/5.0/personalization/raptor_integration/raptor_recommendation_blocks/#other-customers-have-also-purchased-block) - [The Personal Shopping Assistant](https://doc.ibexa.co/projects/userguide/en/5.0/personalization/raptor_integration/raptor_recommendation_blocks/#the-personal-shopping-assistant-block) - [User's item history](https://doc.ibexa.co/projects/userguide/en/5.0/personalization/raptor_integration/raptor_recommendation_blocks/#users-item-history-block) *[Image: Recommendation blocks]* After opening the settings of a recommendation block, a link is available at the bottom of the window. It leads to the [Raptor Control Panel](https://controlpanel.raptorsmartadvisor.com/) (opens in a separate tab), where you can configure advanced settings and fine-tune the recommendation strategy. *[Image: Advanced settings]* For a complete description of Recommendation blocks see [Recommendation blocks](https://doc.ibexa.co/projects/userguide/en/5.0/recommendations/raptor_integration/raptor_recommendation_blocks/). For the list of all page blocks that are available in Page Builder, see [Block reference page](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/block_reference/). # Custom recommendation rendering You can use existing controllers to render [recommendations](https://doc.ibexa.co/en/latest/recommendations/raptor_integration/recommendation_blocks/index.md) outside the Page Builder. The controllers responsible for rendering block recommendations on the front-end are independent and can be used to render recommendations for specific strategies. Each controller can be used to retrieve and display recommendations within a Twig template as follows: ``` {{ render(controller('', { 'limit': limit, 'template': '@ibexadesign/.html.twig', '': parameter_value })) }} ``` The controllers are placed in the `Ibexa\Bundle\ConnectorRaptor\Controller\Block` namespace. Each controller always requires these two parameters: - **limit** – the number of recommendations to render - **template** – the path to the template Any other required parameters are specific to each controller and are detailed in the **Parameters** column of the table below: | Block name | Controller | Parameters | Recommendation item type | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------- | --------------------------------------------------------------------------------------- | ------------------------ | | [Content that have been seen along with the item category](https://doc.ibexa.co/projects/userguide/en/5.0/personalization/raptor_integration/raptor_recommendation_blocks/#content-that-have-been-seen-along-with-the-item-category-block) | `ContentBasedOnProductCategoryBlockController` `::showAction()` | `categoryId` (integer), `limit` (integer), `template` (string) | Content | | [Most popular content](https://doc.ibexa.co/projects/userguide/en/5.0/personalization/raptor_integration/raptor_recommendation_blocks/#most-popular-content-block) | `PopularContentBlockController` `::showAction()` | `limit` (integer), `template` (string) | Content | | [Most popular products](https://doc.ibexa.co/projects/userguide/en/5.0/personalization/raptor_integration/raptor_recommendation_blocks/#most-popular-products-block) | `PopularItemsBlockController` `::showAction()` | `showInStock` (boolean), `limit` (integer), `template` (string) | Product | | [Most popular products in category](https://doc.ibexa.co/projects/userguide/en/5.0/personalization/raptor_integration/raptor_recommendation_blocks/#most-popular-products-in-category-block) | `PopularItemsInCategoryBlockController` `::showAction()` | `categoryId` (integer), `showInStock` (boolean), `limit` (integer), `template` (string) | Product | | [Other customers have also seen](https://doc.ibexa.co/projects/userguide/en/5.0/personalization/raptor_integration/raptor_recommendation_blocks/#other-customers-have-also-seen-block) | `SimilarItemsBlockController` `::showAction()` | `productCode` (string), `showInStock` (boolean), `limit` (integer), `template` (string) | Product | | [Other customers have also seen this content](https://doc.ibexa.co/projects/userguide/en/5.0/personalization/raptor_integration/raptor_recommendation_blocks/#other-customers-have-also-seen-this-content-block) | `SimilarContentBlockController` `::showAction()` | `contentId` (integer), `limit` (integer), `template` (string) | Content | | [Other Customers Have also Purchased block](https://doc.ibexa.co/projects/userguide/en/5.0/personalization/raptor_integration/raptor_recommendation_blocks/#other-customers-have-also-purchased-block) | `OtherCustomersAlsoPurchasedBlockController` `::showAction()` | `productCode` (string), `limit` (integer), `template` (string) | Product | | [Personalized content recommendations](https://doc.ibexa.co/projects/userguide/en/5.0/personalization/raptor_integration/raptor_recommendation_blocks/#personalized-content-recommendations-block) | `UserContentRecommendationsBlockController` `::showAction()` | `limit` (integer), `template` (string) | Content | | [The Personal Shopping Assistant](https://doc.ibexa.co/projects/userguide/en/5.0/personalization/raptor_integration/raptor_recommendation_blocks/#the-personal-shopping-assistant-block) | `UserItemRecommendationsBlockController` `::showAction()` | `productCode` (string), `showInStock` (boolean), `limit` (integer), `template` (string) | Product | | [User's item history](https://doc.ibexa.co/projects/userguide/en/5.0/personalization/raptor_integration/raptor_recommendation_blocks/#users-item-history-block) | `UserItemHistoryBlockController` `::showAction()` | `showInStock` (boolean), `limit` (integer), `template` (string) | Product | Each template receives a `recommendations` Twig variable, which is a list with either [`Ibexa\Contracts\ProductCatalog\Values\ProductInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-ProductInterface.html) instances for product recommendations or [`Ibexa\Contracts\Core\Repository\Values\Content\Content`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Content.html) instances for content recommendations. Two generic templates are provided and can be used in `./templates/themes/` directory: - `@ibexadesign/ibexa/recommendations/_content_list.html.twig` for content items: ``` {% if recommendations is not empty %} {% for content in recommendations %} {% set location = content.contentInfo.mainLocation %} {% if location %}

    {{ ibexa_content_name(content) }}

    {% else %}

    {{ ibexa_content_name(content) }}

    {% endif %} {% endfor %} {% endif %} ``` - `@ibexadesign/ibexa/recommendations/_product_list.html.twig` for products: ``` {% if recommendations is not empty %} {% endif %} ``` To fetch recommendations for the remaining modules, you need to [create a custom controller](https://doc.ibexa.co/en/latest/templating/queries_and_controllers/controllers/index.md) and use a method from [`Ibexa\Contracts\ConnectorRaptor\Recommendations\RecommendationsServiceInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorRaptor-Recommendations-RecommendationsServiceInterface.html). Use this method to display recommendations on any page, for example, on a specific product page, as shown below: *[Image: Custom rendering]* # CDP (Customer Data Platform) # Customer Data Platform (CDP) Editions: Experience ## What is Ibexa CDP Ibexa CDP helps you solve one of the hardest challenges facing business world today: building unique experiences for your customers. With Ibexa CDP you're able to track and aggregate data of your customers' activity on multiple channels. It allows you to create individual customer profiles that enable you to personalize their experience on your platform. *[Image: Ibexa CDP control panel]* ## How it works Ibexa CDP unifies customer data across your organization to help you activate your users and provide them with real-time engagement. With defined audiences you can target your user segments at the right time, through the most used channel, with the relevant message, content, or products. The customer data are collected through the system of trackers embedded in different parts of your page. For more information on activation and trackers, see [CDP activation documentation](https://doc.ibexa.co/en/latest/cdp/cdp_activation/cdp_activation/index.md). # Ibexa Customer Data Platform (CDP) product guide Editions: Experience ## What is Ibexa CDP Ibexa CDP is a Customer Data Platform module that helps you build unique and memorable experiences for your customers. By using Ibexa CDP you can monitor and compile data about your customers' activity on multiple channels. It also allows you to create individual customer profiles so you can customize their experience on your platform. With Ibexa CDP you can store and manage large volumes of customer data in a structured manner. This central data storage supports business growth with a scalable infrastructure, helping to futureproof your business. You can get customer data from both online and offline data sources. It includes first, second, and third-party data from multiple sources such as transactional systems, website tracking, and behavior, POS, CRM, and others. ## Availability Ibexa CDP is available in Ibexa Experience and Ibexa Commerce editions. ## How does Ibexa CDP work Ibexa CDP unifies customer data throughout your whole organization. It helps you activate your users and give them real-time interaction. You can target certain user segments with the appropriate message, content, or products at the right time through the most used channels by using specified audiences. Customer data is gathered through a system of trackers embedded in various areas of your website. *[Image: CDP - how does it work]* ### Installation and configuration To start using Ibexa CDP, first you need to contact your sales representative, who provides you with a link to [register your Ibexa CDP account](https://doc.ibexa.co/en/latest/cdp/cdp_installation/#register-in-ibexa-cdp-dashboard). When you're done with registration process, you're able to access a separate instance with the data needed to configure, activate, and use this feature. After your account is created, you can [download and install the Ibexa CDP package](https://doc.ibexa.co/en/latest/cdp/cdp_installation/#install-cdp-package) that is opt-in and needs to be downloaded separately. Last step is to go through the [configuration process](https://doc.ibexa.co/en/latest/cdp/cdp_activation/cdp_configuration/). ### Customer profile In Ibexa CDP you can build 360° customer profiles. It unifies customer data from different sources to help you understand your prospects and customer needs. After you get customer data, you can unify and match customer profiles based on their preferences and habits. You can create and analyze complete, 360° customer profiles based on demographics, interactions, behaviors, and transactional data. This approach helps you create a single customer view. *[Image: Customer profile]* ### Segment groups To create a personalized customer experience, you need to group your clients into specified audiences. Ibexa DXP comes with a ready solution - segment groups. Segment group information is reused by various Ibexa DXP functionalities, such as [Personalization](https://doc.ibexa.co/en/latest/personalization/personalization_guide/index.md) or content targeting. You can [create a segment group](https://doc.ibexa.co/projects/userguide/en/5.0/personalization/segment_management/) in the back office of Ibexa DXP. It serves as a container for all segments data generated by Ibexa CDP. When you create a segment group, you need to provide its name and identifier. Be careful while doing so, as after you create the segment group in the back office and connect it to Ibexa CDP, you cannot change it in any way, including edit its name. Remember to add a segment group identifier to the configuration, under the `segment_group_identifier` field. ## Capabilities ### Data export Configuration in Ibexa CDP allows you to automate the process of exporting content, users, and products. An `ibexa_cdp.data_export` [configuration key](https://doc.ibexa.co/en/latest/cdp/cdp_data_export_schedule/#configuration-key) includes the `schedule` setting where you can find separate sections for exporting user, content, and product. Structure of each section is exactly the same and includes `interval` and `options` elements: - `interval` - sets the frequency at which the command is invoked, uses cron expressions, for example, '\*/30 * * * \*' means "every 30 minutes", '0 \*/12 * * \*' means "every 12th hour" - `options` - allows you to add arguments that have to be passed to the export command This configuration allows you to provide multiple export workflows with parameters. It's important, because all the types of content/product must have their own parameters on the CDP side, where each has a different Stream ID key and different required values configured per data source. Regarding data export, currently, only Stream File transport is supported and can be initialized from the configuration. For more information, see [CDP data export](https://doc.ibexa.co/en/latest/cdp/cdp_activation/cdp_data_export/). ### Data customization ​You can customize content and product data exported to Ibexa CDP and control what field type information you want to export. With Ibexa CDP, you can export field types and field type values. They're exported with metadata and attributes, for example, ID, field definition name, type, or value. For more information, see [data customization](https://doc.ibexa.co/en/latest/cdp/cdp_data_customization/#data-customization) documentation in Developer Documentation. ### Client-side Tracking The final step is setting up a tracking script. For more information, see [CDP add client-side tracking](https://doc.ibexa.co/en/latest/cdp/cdp_activation/cdp_add_clientside_tracking/) and [Introduction to tracking in Raptor documentation](https://content.raptorservices.com/help-center/introduction-to-tracking-documentation). ### Audience Builder In the Audience Builder, you can create audiences - groups of users that meet the assumed conditions. You can choose specific conditions: `did`, `did not`, or `have`. The conditions `did` and `did not` allow you to use events like buy, visit or add to a cart from online tracking. The `have` conditions are tied to personal characteristics and can be used to track the sum of all buys or top-visited categories. You can also connect created audiences to the activations. *[Image: Audience Builder]* ## Benefits ### Personalized user experience With Ibexa CDP you can build unique and memorable experience for your customers and create individual customer profiles. By using 360° client profiles, you can connect with the right customer at the right moment, in the right place. Build extensive customer profiles that include their interactions, habits, and preferences from several touchpoints. ### Segment groups Provide a personalized customer experience, group your clients into specified audiences, and provide recommendations depending on the user data. Create segment groups to deliver personalized campaigns to boost engagement and conversion rates. ### Audience Builder Create user groups - audiences - based on conditions and events. ### Data export Export data regarding content, users, and products. Data export includes automatic file mapping. Analyze customer data, track campaigns, and discover the most effective strategies to boost performance. ### Data customization Customize data to control what field type information you want to export. ### Real-time action Deliver relevant interactions in the right place at the right time for optimal results thanks to dynamic, real-time data updates. Take advantage of event-triggered communications which are aligned with your customers immediate interests. # Ibexa CDP installation Editions: Experience There are three steps required to install Ibexa CDP. First, you need to register your Ibexa CDP account, then you can download a CDP package and update the configuration. ## Register in Ibexa CDP dashboard If you decide to acquire Ibexa CDP contact your sales representative, they provide you with a registration link to Ibexa CDP. After registration, you get access to a separate instance where you can find data required for configuring, activating, and using this feature. ## Install CDP package Ibexa CDP comes in an additional package that is opt-in and needs to be downloaded separately. To download it run: ``` composer require ibexa/cdp ``` Symfony Flex installs and activates the package. After an installation process is finished, go to `config/packages/security.yaml` and uncomment `ibexa_cdp` rule. ``` ibexa_cdp: pattern: /cdp/webhook guard: authenticator: 'Ibexa\Cdp\Security\CdpRequestAuthenticator' stateless: true ``` Now, you can configure Ibexa CDP. Go to [the activation documentation](https://doc.ibexa.co/en/latest/cdp/cdp_activation/cdp_activation/index.md) and follow the steps. # CDP Activation Editions: Experience Follow a step-by-step procedure that allows you to activate Ibexa CDP. Activation includes configuration, data export and adding Client-side Tracking. - [Configuration](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/cdp/cdp_activation/cdp_configuration/): Step-by-step configuration procedure of Ibexa CDP. - [Data export](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/cdp/cdp_activation/cdp_data_export/): Step-by-step data export procedure in Ibexa CDP. - [Add Client-side Tracking](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/cdp/cdp_activation/cdp_add_clientside_tracking/): Client-side Tracking in Ibexa CDP. # Configuration Editions: Experience To configure Ibexa CDP, use the `ibexa.system..cdp` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa: system: default: cdp: account_number: 123456 data_export: user_data: transport: stream_file stream_file: stream_id: 00000000-00000000-00000000-00000000 content_data: transport: stream_file stream_file: stream_id: 00000000-00000000-00000000-00000000 product_data: transport: stream_file stream_file: stream_id: 00000000-00000000-00000000-00000000 activations: - client_id: '%env(CDP_ACTIVATION_CLIENT_ID)%' client_secret: '%env(CDP_ACTIVATION_CLIENT_SECRET)%' segment_group_identifier: example_segment_group_identifier ``` - `account_number` - a [number](#account-number) obtained from Accounts settings in Ibexa CDP dashboard - `stream_id` - stream ID generated when importing data from the stream file in Data Manage - `activations` - activation details. You can configure multiple activations. They have to be of type `Ibexa` in Ibexa DXP dashboard - `client_id` and `client_secret` - client credentials are used to authenticate against the Webhook endpoint. Make sure they're random and secure - `segment_group_identifier` - a [location](#segment-group) to which CDP data is imported ## Account number Now, fill in the account number. Log in to Ibexa CDP and in the top right corner, select available accounts. *[Image: List of available accounts]* A pop-up window displays a list of all available accounts and their numbers. *[Image: Account number]* ## Segment group Create a segment group in the back office. It serves as a container for all segments data generated by Ibexa CDP. Go to **Admin** -> **Segments** and select **Create**. Fill in name and identifier for a segment group. Choose wisely, as once connected to CDP segment group cannot be changed. > **Caution: Ibexa CDP segment group** > > After you create the segment group in the back office and connect it to Ibexa CDP, you cannot change it in any way, including edit its name. *[Image: Creating a new segment group]* Next, add a segment group identifier to the configuration. # Data export Editions: Experience You need to specify a source of the user data that Ibexa CDP connects to. To do so, go to **Data Manager** in **Tools** section and select **Create new dataflow**. It takes you to a Dataflow Creator, where in five steps you can set up a data streaming. ## General Information In the **General Information** section, specify dataflow name, choose **Stream File** as a source of user data and **CDP** as a destination, where they're sent for processing. Currently, only Stream File transport is supported and can be initialized from the configuration. ## Download In the **Download** section, select **Stream file**. Copy generated steam ID and paste it into the configuration file under `stream_id`. It allows you to establish a datastream from the Streaming API into the Data Manager. Next, you need to export your data to the CDP. Go to your installation and use this command: - for User: ``` php bin/console ibexa:cdp:stream-user-data --draft ``` - for Product: ``` php bin/console ibexa:cdp:stream-product-data --draft ``` - for Content: ``` php bin/console ibexa:cdp:stream-content-data --draft ``` There are two versions of this command `--draft/--no-draft`. The first one is used to send the test user data to the Data Manager. If it passes a validation test in the **Activation** section, use the latter one to send a full version. You can extend exported user data with custom fields from your user content, such as date of birth, preferences, or other profile information. For more information, see [Data customization](https://doc.ibexa.co/en/latest/cdp/cdp_data_customization/#export-additional-user-data). Next, go back to Ibexa CDP and select **Validate & download**. If the file passes, you can see a confirmation message. Now, you can go to the **File mapping** section. ## File mapping Mapping is completed automatically, the system fills all required information and shows available columns with data points on the right. You can change their names if needed or disallow empty fields by checking **Mandatory**. If the provided file contains empty values, this option isn't available. If provided file isn't recognized, the system requires you to fill in the parsing-options manually or select an appropriate format. If you make any alterations, select the **Parse File** to generate columns with new data. ## Transform & Map In the **Transform & Map** section you transform data and map it to a schema. At this point, you can map **email** to **email** and **id** to **integer** fields to get custom columns. If you have [extended user data export with custom fields](https://doc.ibexa.co/en/latest/cdp/cdp_data_customization/#export-additional-user-data), those fields appear as additional columns in this section. Make sure to add them to your schema in Raptor so they can be used for segmentation and personalization. Next, select **Create schema based on the downloaded columns**. It moves you to Schema Creator. There, choose **PersonalData** as a parent and name the schema. *[Image: Create new schema]* Next, select all the columns and set Person Identifier as **userid**. *[Image: Person Identifier]* If you used PersonData or Catalog type schemas, the system requires specifying the Write Mode that is applied to them. **Append** (default one) allows new data to overwrite the old one but leaves existing entries unaffected. All entries are stored in the dataset, unchanged by updating dataflow. For example, if a customer unsubscribes a newsletter, their email remains in the system. **Overwrite** completely removes the original dataset and replaces it with the new one every time the dataflow runs. Next, select **userid** from a **Schema columns section** on the right and map it to **id**. *[Image: Map userid to id]* ## Activation In this section you can test the dataflow with provided test user data. If everything passes, go to your installation and export production data with this command: ``` php bin/console ibexa:cdp:stream-user-data --no-draft ``` Now you can run and activate the dataflow. ## Build new Audience/Segment Go to the **Audience Builder** and select **Build new audience**. When naming the audience remember, you need to find it in a drop-down list during activation. There, you can choose conditions from `did`, `did not` or `have`. The conditions `did` and `did not` allow you to use events like buy, visit or add to a cart from online tracking. - `have` conditions are tied to personal characteristics and can be used to track the sum of all buys or top-visited categories. In the Audience Builder, you can also connect created audiences to the activations. ## Activation Activation synchronises data from Ibexa CDP to the Ibexa DXP. When you specify a segment, you can activate it on multiple communication channels, such as newsletters or commercials. You can configure multiple activations based data flows. First, from the menu bar, select **Activations** and create a new **Ibexa** activation. Specify name of your activation, select `userid` as **Person Identifier** and click **Next**. *[Image: General Information - Activation]* Next, you can fill in **Ibexa information** they must match the ones provided in the YAML configuration: - **Client Secret** and **Client ID** - are used to authenticate against Webhook endpoint. In the configuration they're taken from environment variables in `.env` file. - **Segment Group Identifier** - identifier of the segment group in Ibexa DXP. It points to a segment group where all the CDP audiences are stored. - **Base URL** - URL of your instance with added `/cdp/webhook` at the end. *[Image: Ibexa Information - Activation]* Finally, you can specify the audiences you wish to include. > **Note: CDP requests** > > All CDP requests are logged in with `debug` severity. ### Ibexa Messenger support for large batches of data CDP uses Ibexa Messenger to process incoming data from [Raptor](https://www.raptorservices.com/). This approach improves performance and reliability when processing large amounts of CDP user records. For more information, see [Background tasks: How it works](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/background_tasks/#how-it-works). By using Messenger while working with large batches of data, requests are queued instead of being processed synchronously: - queuing items starts automatically once a certain number of actions is reached (below this number, items are processed in a single request, using the standard synchronous behavior) - every single data is recorded in the database - a background worker retrieves records from the queue, processing them one by one or in batches, depending on the [Messenger](https://symfony.com/doc/7.4/messenger.html) configuration - processing happens at set intervals to avoid timeouts and keep the system stable 1. Make sure that the transport layer is [defined properly](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/background_tasks/#configure-package) in Ibexa Messenger configuration. 1. Add `bulk_async_threshold` setting in the `config/packages/ibexa_cdp.yaml` configuration: ``` ibexa_cdp: bulk_async_threshold: 100 # Default: 100 items ``` Available options: - `bulk_async_threshold` (integer, default: 100) - minimum number of items required to trigger asynchronous processing - below threshold - items are processed immediately in a single request, using the standard synchronous behavior - at/above threshold - items are automatically dispatched to the asynchronous queue for background processing 3. Make sure that the [worker starts](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/background_tasks/#start-worker) together with the application to watch the transport bus: ``` php bin/console messenger:consume ibexa.messenger.transport --bus=ibexa.messenger.bus ``` For more information, see [Start background task worker](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/background_tasks/#start-worker). ### CDP Monolog channel CDP Monolog channel handles webhook logs for easier separation of logs. ``` - { name: monolog.logger, channel: ibexa.cdp.webhook } ``` It's possible to configure `ibexa.cdp.webhook` Monolog channel to direct all logs to specific stream, file, or service. This allows webhook logs to be stored separately from the main application logs for easier debugging and analysis. To do it, in `config/packages/monolog.yaml` file, define a new handler for the `ibexa.cdp.webhook` channel that directs CPD Webhook events to a separate file. It can be configured in both `dev` and `prod` environments, for example: ``` monolog: handlers: cdp_webhook: type: stream path: "%kernel.logs_dir%/cdp_webhook_%kernel.environment%.log" level: debug channels: [ 'ibexa.cdp.webhook' ] ``` If you want to avoid redundant or duplicate entries in the other logs, exclude the webhook channel by: ``` channels: ["!ibexa.cdp.webhook"] ``` # Add Client-side Tracking Editions: Experience The final step is setting up a tracking script. It requires a head tracking script between the `` tags on your website, a main script after the head script, and cookie consent. For more information about setting up a tracking script, see [Raptor documentation](https://content.raptorservices.com/help-center/client-side-tracking). Now, you need to add a tracker to specific places in your website where you want to track users. For example, add this tracker to the landing page template to track user entrances. ``` raptor.trackEvent('visit', ..., ...); ``` or purchases: ``` //Parameters for Product 1 raptor.trackEvent('buy', ..., ...); //Parameters for Product 2 raptor.trackEvent('buy', ..., ...); ``` For tracking to be effective, you also need to send ID of a logged-in user in the same way. Add the user ID information by using below script: ``` raptor.push("setRuid","USER_ID_HERE") ``` For more information on tracking events, see [the documentation](https://content.raptorservices.com/help-center/tracking-events-for-recommendation). # CDP data export schedule Editions: Experience ## Configuration key Configuration in Ibexa CDP allows you to automate the process of exporting content, users, and products. An `ibexa_cdp.data_export` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files) looks as below: ``` ibexa_cdp: data_export: schedule: user: - interval: '*/15 * * * *' options: '--stream-id=00000000-00000000-00000000-00000000 --user-content-type=user --no-draft' - interval: '0 */6 * * *' options: '--stream-id=00000000-00000000-00000000-00000000 --user-content-type=user --no-draft' content: - interval: '*/30 * * * *' options: '--stream-id=00000000-00000000-00000000-00000000 --content-type=article --no-draft' - interval: '0 */12 * * *' options: '--stream-id=00000000-00000000-00000000-00000000 --content-type=article --no-draft' product: - interval: '*/30 * * * *' options: '--stream-id=00000000-00000000-00000000-00000000 --product-type=computer --no-draft' - interval: '0 */12 * * *' options: '--stream-id=00000000-00000000-00000000-00000000 --product-type=computer --no-draft' ``` Under the `schedule` setting you can find separate sections for exporting user, content, and product. Structure of each section is exactly the same and includes `interval` and `options` elements: - `interval` - sets the frequency of the command invoke, for example, '\*/30 * * * \*' means "every 30 minutes", '0 \*/12 * * \*' means "every 12th hour". It uses a standard `crontab` format, see [examples](https://crontab.guru/examples.html). - `options`- allows you to add arguments that have to be passed to the export command. This configuration allows you to provide multiple export workflows with parameters. It's important, because each type of content/product must have its own parameters on the CDP side, where each has a different Stream ID key and different required values, which are configured per data source. Accepted options can be listed with the command below: - for User: ``` php bin/console ibexa:cdp:stream-user-data --help ``` - for Product: ``` php bin/console ibexa:cdp:stream-product-data --help ``` - for Content: ``` php bin/console ibexa:cdp:stream-content-data --help ``` # Data customization Editions: Experience ​ You can customize user, content, and product data exported to CDP and you can control what field type information you want to export. By default, custom field types have basic export functionality. It casts their `Value` object to string, thanks to `\Stringable` implementation. ​ ## Export additional user data You can extend user data exported to CDP by attaching custom information, for example user content fields or user preferences. Use it for advanced customer segmentation and personalization in marketing campaigns. To add custom data to user exports, create a class that extends [`\Ibexa\Contracts\Cdp\Export\User\AbstractUserItemProcessor`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Cdp-Export-User-AbstractUserItemProcessor.html) and implement the `doProcess()` method. The base class handles user field validation and provides helper methods for working with user content. The following example adds a custom date of birth field to the exported data: ``` getUserField($userContent); if (null === $userField) { throw new InvalidArgumentException('Content does not contain user field'); } $dateOfBirth = ''; $dateOfBirthField = $userContent->getField($this->dateOfBirthFieldIdentifier); if ($dateOfBirthField !== null && $dateOfBirthField->value instanceof DateValue && $dateOfBirthField->value->date !== null ) { $dateOfBirth = $dateOfBirthField->value->date->format('Y-m-d'); } return array_merge( $processedItemData, [ 'date_of_birth' => $dateOfBirth, ] ); } } ``` Register your processor as a Symfony service and tag it with `ibexa.cdp.export.user.item_processor`: ``` App\Export\User\DateOfBirthUserItemProcessor: parent: Ibexa\Contracts\Cdp\Export\User\AbstractUserItemProcessor arguments: $dateOfBirthFieldIdentifier: 'date_of_birth' tags: - { name: 'ibexa.cdp.export.user.item_processor', priority: 100 } ``` The `priority` parameter controls the order of execution when multiple processors are registered. Higher priority values run first. Your custom processor can modify the data returned from the previous processors, for example by adding new entries or modifying the existing ones. The exported user data includes your custom fields: ``` { "date_of_birth": "2000-01-01", "id": 1, "login": "example", "email": "example@example.org", "name": "John Doe", } ``` ## Export field types ​ Field types are exported with metadata, for example, ID, field definition name, type, or value. You can also provide your own [`\Ibexa\Contracts\Cdp\Export\Content\FieldProcessorInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Cdp-Export-Content-FieldProcessorInterface.html) instance to extend metadata. The provided implementation has to be defined as a service and tagged with `ibexa.cdp.export.content.field_processor`. Additionally, you can specify `priority` to override the default behavior. All system Field Processors use `-100` priority, and any higher priority value overrides them. The interface is plain and has two methods that you need to provide: - **supports** - decides whether your `FieldProcessor` can work with the `Field` instance. - **process** - takes `Field` instance and then returns a flat array of scalar values that are combined with the payload data. ​ A common field type is serialized to: ​ ``` { "field_measurement_simple_id": 1792, "field_measurement_simple_type": "ibexa_measurement", "field_measurement_simple_language_code": "eng-GB", "field_measurement_simple_value_measurement": "data transfer rate", "field_measurement_simple_value_unit_identifier": "megabyte per second", "field_measurement_simple_value_unit_symbol": "MB/s", "field_measurement_simple_value_unit_is_base": false, "field_measurement_simple_value_base_unit_identifier": "bit per second", "field_measurement_simple_value_base_unit_symbol": "bit/s", "field_measurement_simple_value_simple": 100, "field_measurement_simple_value_simple_base_unit": 800000000 } ``` ​ Field identifier is a prefix that is automatically added to each key. You can only use scalar values. ​ ### Built in Field Processors for custom field types ​ You can provide your own CDP export functionality by using one of the system Field Processors: #### `\Ibexa\Cdp\Export\Content\FieldProcessor\SkippingFieldProcessor` ​ It results in the field type being excluded from the exported payload. To avoid adding the field type data to the payload, register a new service as follows: ​ ``` custom_fieldtype.cdp.export.field_processor: class: Ibexa\Cdp\Export\Content\FieldProcessor\SkippingFieldProcessor autoconfigure: false arguments: $fieldTypeIdentifier: custom_fieldtype tags: - { name: 'ibexa.cdp.export.content.field_processor', priority: 0 } ``` ​ ## Export field type values ​ To customize export of field type values, provide your own [`\Ibexa\Contracts\Cdp\Export\Content\FieldValueProcessorInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Cdp-Export-Content-FieldValueProcessorInterface.html) instance. New implementation has to be registered as a service manually or by using autoconfiguration. The service has to use the tag `ibexa.cdp.export.content.field_value_processor`. You can also provide `priority` property to override other Field Value Processors. ​ * `FieldValueProcessorInterface::process` - takes `Field` instance and returns an `array` with scalar values that are applied to export data payload. If the field type returns a single value, provides a `value` key in the array. You can return multiple values. - `FieldValueProcessorInterface::supports` - decides whether `FieldValueProcessor` can work with the `Field`. ​ ### Built in Field Value Processors for custom field types ​ Several system Field Value Processors either work by default or can be registered for custom field types: ​ #### `\Ibexa\Cdp\Export\Content\FieldValueProcessor\CastToStringFieldValueProcessor` ​ This Processor is a default one, as long as no other Processor with higher priority is registered. It makes `\Stringable` implementation of the field type `\Ibexa\Core\FieldType\Value` object to use it as a value in the final payload. ​ #### `\Ibexa\Cdp\Export\Content\FieldValueProcessor\JsonHashFieldValueProcessor` ​ This Processor generates JSON data from hash representation of the field type (it uses [`\Ibexa\Contracts\Core\FieldType\FieldType::toHash`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-FieldType-FieldType.html#method_toHash) method). > **Caution: Caution** > > CDP doesn't support column mapping, which allows you to match records on JSON data directly. To use `JsonHashFieldValueProcessor`, you need to register a new service: ​ ``` custom_fieldtype.cdp.export.field_processor: class: Ibexa\Cdp\Export\Content\FieldValueProcessor\JsonHashFieldValueProcessor autoconfigure: false arguments: $fieldTypeIdentifier: custom_fieldtype tags: - { name: 'ibexa.cdp.export.content.field_value_processor', priority: 0 } ``` # Search # Search Ibexa DXP exposes a very powerful [Search API](https://doc.ibexa.co/en/latest/search/search_api/index.md), allowing both full-text search and querying the content repository by using several built-in Search Criteria and Sort Clauses. These are supported across different search engines, allowing you to plug in another search engine without changing your code. - [Search engines](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/search/search_engines/search_engines/): Learn about different search engines that are supported by Ibexa DXP. - [Elasticsearch search engine](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/search/search_engines/elasticsearch/elasticsearch_overview/): Elasticsearch search engine overview. - [Solr search engine](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/search/search_engines/solr_search_engine/solr_overview/): Solr search engine overview. - [Search API](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/search/search_api/): You can search for content, locations and products by using the PHP API. Fine-tune the search with Search Criteria, Sort Clauses and Aggregations. - [Search Criteria and Sort Clauses](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/search/search_criteria_and_sort_clauses/): Search Criteria and Sort Clauses help you fine-tune searches done by using the Search API. - [Create custom Search Criterion](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/search/extensibility/create_custom_search_criterion/): Create custom Search Criterion to use with Solr and Elasticsearch search engines. - [Create custom Sort Clause](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/search/extensibility/create_custom_sort_clause/): Create custom Sort Clause to use with Solr and Elasticsearch search engines. - [Create custom Aggregation](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/search/extensibility/create_custom_aggregation/): Create custom Aggregation to use with Solr and Elasticsearch search engines. # Search engines Ibexa DXP enables you to use different search engines. Currently, they exist in their own Ibexa DXP Bundles: 1. [Legacy search engine](https://doc.ibexa.co/en/latest/search/search_engines/legacy_search_engine/legacy_search_overview/index.md) - a database-powered search engine for basic needs. 2. [Solr](https://doc.ibexa.co/en/latest/search/search_engines/solr_search_engine/solr_overview/index.md) - an integration providing better overall performance, better scalability and support for more advanced search capabilities. 3. [Elasticsearch](https://doc.ibexa.co/en/latest/search/search_engines/elasticsearch/elasticsearch_overview/index.md) - a document-oriented engine providing even better performance and scalability. ## Search engines comparison | Feature | Legacy Search Engine (SQL) | Solr | Elasticsearch | | --------------------------- | -------------------------- | ---- | ------------------------- | | Filtering | Yes, limited\* | Yes | Yes | | Query (filter with scoring) | Only filters, no scoring | Yes | Yes | | Full-text search | Yes, limited\*\* | Yes | Yes, limited | | Index-time boosting | No | No | Query-time boosting\*\*\* | | Aggregations | No | Yes | Yes | \* Usage of Criteria and Sort Clauses for fields doesn't perform well on medium to larger amount of data with Legacy Search Engine (SQL). \*\* For more information about full-text search syntax support, see [Full-text Criterion](https://doc.ibexa.co/en/latest/search/criteria_reference/fulltext_criterion/index.md). \*\*\* Elasticsearch offers query-time boosting instead. # Elasticsearch search engine Elasticsearch is an open-source, distributed, Java-based search engine that responds to queries in real-time and is scalable in reaction to changing processing needs. Elasticsearch enables you to use filtering, query, query-time boosting, full-text search, and aggregations. It organizes data into documents, that then are grouped into indices. As a result of having distributed architecture, Elasticsearch can analyze massive amounts of data with almost real-time performance. Instead of searching text directly, it searches and index. Thanks to this mechanism, it's able to achieve fast response. For a detailed description of advanced settings that you might require in a specific production environment, see the documentation provided by Elastic. Start with the [Set up Elasticsearch](https://www.elastic.co/guide/en/elasticsearch/reference/8.19/setup.html) section. **Prerequisite** To proceed you need to be familiar with how indexing, filtering and queries work. ## Update Elasticsearch schema Whenever you make any changes in case of variables (for example, environmental ones) or configuration files, you need to erase Elasticsearch index, update the schema, and rebuild the index. To delete an index, you can use the Elasticsearch's REST API. First, use the [`_cat/indices` endpoint](https://www.elastic.co/guide/en/elasticsearch/reference/8.19/cat-indices.html) to list existing indices. For example, the command `curl -H "Accept: application/text" elasticsearch:9200/_cat/indices` returns output like the following: ``` yellow open default_location_eng_gb_54 DoSFV-CtQFylKKVvd48YfA 1 1 1 0 16.7kb 16.7kb yellow open default_location_eng_gb_42 3Z_IrWVHQh2m37jPqQBOcQ 1 1 1 0 20.1kb 20.1kb yellow open default_content_eng_gb_45 y-t4uNQwR4KRJ-N9i3zUog 1 1 1 0 21.3kb 21.3kb yellow open default_content_eng_gb_46 e_LS5qG3RIih6iQRPsNp-w 1 1 1 0 22.5kb 22.5kb yellow open default_content_eng_gb_1 101-1-tQS_2KSvNs2X2JAQ 1 1 17 0 39.8kb 39.8kb yellow open default_location_eng_gb_46 fSGtpljwTpGfascFechmww 1 1 1 0 21kb 21kb (...) ``` Create a list containing all indices used by the DXP, including the [custom indices](https://doc.ibexa.co/en/latest/search/search_engines/elasticsearch/configure_elasticsearch/#define-field-type-mapping-templates) as well. Then, delete them by using the [delete index endpoint](https://www.elastic.co/guide/en/elasticsearch/reference/8.19/indices-delete-index.html) ``` curl --request DELETE 'https://elasticsearch:9200/default_location*' curl --request DELETE 'https://elasticsearch:9200/default_content*' (...) ``` > **Tip: Tip** > > To quickly delete all existing Elasticsearch indices, you can use the `_all` keyword as the name of the index, as in the following request: `curl --request DELETE https://elasticsearch:9200/_all`. Always review the list of existing indices and confirm they are safe to delete before executing this command, as it permanently removes data. To update the schema and then reindex the search, use the following commands: ``` php bin/console ibexa:elasticsearch:put-index-template --overwrite php bin/console ibexa:reindex ``` # Install Elasticsearch ## Download and install Elasticsearch [Install Elasticsearch](https://www.elastic.co/guide/en/elasticsearch/reference/8.19/install-elasticsearch.html) on your server. As an example, use the following [Docker](https://docs.docker.com/get-started/docker-overview/) command: ``` docker run -d --name ibexa-dxp-elasticsearch -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:8.19.0 ``` > **Note: Note** > > Ibexa DXP supports Elasticsearch in versions 7.16 and 8.19. ## Verify the instance To make sure that the Elasticsearch instance operates properly, access the instance, for example, with `curl http://localhost:9200/`. If Elasticsearch operates properly, an object with cluster details is displayed. It should be similar to the following example: ``` { "name" : "f45b86ab3726", "cluster_name" : "docker-cluster", "cluster_uuid" : "5OAEghGPTLSd4jUJColoNQ", "version" : { "number" : "8.19.0", "build_flavor" : "default", "build_type" : "docker", "build_hash" : "93788a8c2882eb5b606510680fac214cff1c7a22", "build_date" : "2025-07-23T22:10:18.138212839Z", "build_snapshot" : false, "lucene_version" : "9.12.2", "minimum_wire_compatibility_version" : "7.17.0", "minimum_index_compatibility_version" : "7.0.0" }, "tagline" : "You Know, for Search" } ``` ## Set the default search engine Set the following environment variable (for example, in the `.env` or `.env.local` file): ``` SEARCH_ENGINE=elasticsearch ``` ## Configure the search engine Ibexa DXP comes pre-configured to work with an Elasticsearch cluster that uses default settings, and you can use this initial setup for testing purposes. However, to effectively search through actual data, you must provide specific settings. All configuration is made in the `/config/packages/ibexa_elasticsearch.yaml` file. > **Note: Note** > > All the settings, their order and meaning, correspond to the settings that are described in the Elasticsearch documentation. First, decide how Ibexa DXP connects to Elasticsearch and configure other connection settings. For more information, see [Configuring connections](https://doc.ibexa.co/en/latest/search/search_engines/elasticsearch/configure_elasticsearch/#configure-connections). Then, define a field type mappings template that instructs Elasticsearch to interpret Ibexa DXP fields as specific types. For more information, see [Configuring field type mappings](https://doc.ibexa.co/en/latest/search/search_engines/elasticsearch/configure_elasticsearch/#define-field-type-mapping-templates). ## Push the templates For each of your defined connections, push the templates to the Elasticsearch engine by running the following command: ``` php bin/console ibexa:elasticsearch:put-index-template ``` You can modify the behavior of the command with a number of switches. Use the `-h` switch to display a complete list of available options. ## Reindex the database After creating index templates, run the following command to reindex your data: ``` php bin/console ibexa:reindex ``` > **Caution: Risks of premature indexing** > > Don't reindex your data before you create index templates. Otherwise, Elasticsearch attempts to use its [dynamic field mapping](https://www.elastic.co/guide/en/elasticsearch/reference/8.19/dynamic-field-mapping.html) feature to create type mappings automatically. # Configure Elasticsearch ## Configure connections To configure Elasticsearch, first, you need to configure the connections. There are two possibilities of connection: - using [cluster of Elasticsearch nodes](#configure-clustering) - using [Elasticsearch Cloud](#configure-elasticsearch-cloud) No matter which option you choose, you have to define the connection settings under the `connections` key. Set a name of the connection: ``` ibexa_elasticsearch: connections: : ``` > **Tip: A default connection** > > If you define more than one connection, for example, to create a separate connection for each repository, you must select the one that Ibexa DXP should use with the following setting: > > ``` > ibexa_elasticsearch: > # ... > default_connection: > ``` Now, you need to decide whether to add a cluster that you administer and manage yourself, or use a cloud solution from Elastic, and configure additional parameters. If you want to connect by using a cluster, follow the instructions below in the [Cluster](#configure-clustering) section. If you want to use Elasticsearch Cloud, skip to [Elasticsearch Cloud](#configure-elasticsearch-cloud) section. ## Configure clustering A cluster consists of nodes. You might start with one node and then add more nodes if you need more processing power. When you configure a node, you need to set the following parameters: - `host` - an IP address or domain name of the host. Default value: `localhost`. - `port` - a port to connect to. Default value: `9200`. If you have several Elasticsearch instances that run on the same host, and want to make them distinct, you can change the default number. - `scheme` - a protocol used to access the node. Default value: `http`. - `path` - by default, path isn't used. Default value: `null`. If you have several Elasticsearch instances that run on the same host, and want to make them distinct, you can define a path for each instance. - `user`/`pass` - credentials, if needed to log in to the host. Default values: `null`. Next, list the addresses of cluster nodes under the `hosts` key: ``` ibexa_elasticsearch: connections: : hosts: - '127.0.0.1:9200' # ... ``` There are several ways that you can use to pass host parameters. The easiest one is to pass them as a string: ``` - https://:9200// ``` You can also pass the host configuration as an object that lists parameter-value pairs, for example, when your authentication settings contain special characters. ``` - { host: '', scheme: 'http', port: 9200, path: '/', user: , pass: } ``` Cluster connection configuration should have the following structure: ``` ibexa_elasticsearch: connections: simple: hosts: - '127.0.0.1:9200' - '127.0.0.1:9201' - '127.0.0.1:9202' localhost: debug: true hosts: - "127.0.0.1:9200" - "b.elasticsearch.loc:9200" - "c.elasticsearch.loc:9200" intranet: debug: true hosts: - "c.elasticsearch.loc:9200" default_connection: simple ``` ### Multi-node cluster behavior When you configure a cluster-based connection, and the cluster consists of many nodes, you can choose strategies that govern how the cluster reacts to changing operating conditions, or how workload is distributed among the nodes. #### Node pool settings With these settings you decide how nodes in the cluster are selected and how failed nodes are resurrected. The node pool manages the list of active nodes, which can change over time due to connectivity issues, host malfunction, or when you add new nodes to the cluster to increase performance. By default, Elasticsearch uses the `SimpleNodePool` algorithm with `RoundRobin` selector and `NoResurrect` strategy. You can customize the node pool behavior with the following settings: ``` : # ... node_pool_selector: Elastic\Transport\NodePool\Selector\RoundRobin node_pool_resurrect: Elastic\Transport\NodePool\Resurrect\NoResurrect ``` For more information and a list of available choices, see [Node pool](https://www.elastic.co/guide/en/elasticsearch/client/php-api/8.19/node_pool.html). > **Tip: Load tests recommendation** > > If you change the node pool settings, it's recommended that you perform load tests to check whether the change doesn't negatively impact the performance of your environment. ##### Number of retries The `retries` setting configures the number of attempts that Ibexa DXP makes to connect to the nodes of the cluster before it throws an exception. By default, `null` is used, which means that the number of retries equals to the number of nodes in the cluster. ``` : # ... retries: null ``` Depending on the node pool settings that you select, Ibexa DXP's reaction to reaching the maximum number of retries might differ. For more information, see [Set retries](https://www.elastic.co/guide/en/elasticsearch/client/php-api/8.19/set-retries.html). ## Configure Elasticsearch Cloud As an alternative to using your own cluster, you can use Elasticsearch Cloud, a commercial SaaS solution. With Elasticsearch Cloud you don't have to build or manage your own Elasticsearch cluster. Also, you do all the configuration and administration in a graphical user interface. To connect to a cloud solution with Ibexa DXP, you must set the `elastic_cloud_id` parameter by providing an alphanumerical ID string that you get from the cloud's user interface, for example: ``` : elastic_cloud_id: 'production:ZWFzdHVzMi5henVyZS5lbGFzdGljLWNsb3VkLmNvbTo5MjQzJGUwZ' ``` With the ID set, you must configure authentication to be able to access the remote environment. ## Configure security Elasticsearch instances support `basic` and `api_key` authentication methods. You select authentication type and configure the settings under the `authentication` key. By default, authentication is disabled: ``` : # ... authentication: type: null ``` If you connect to Elasticsearch hosts outside of your local network, you might also need to configure SSL encryption. ### Basic authentication If your Elasticsearch server is protected by HTTP authentication, you must provide Ibexa DXP with the credentials. In the basic authentication, you must pass the following parameters: ``` # ... authentication: type: basic credentials: [''] ``` For example: ``` ibexa_elasticsearch: connections: cloud: debug: true elastic_cloud_id: 'test:ZWFzdHVzMi5henVyZS5lbGFzdGljLWNsb3VkLmNvbTo5MjQzJGUwZ' authentication: type: basic credentials: ['elastic', '1htFY83VvX2JRDw88MOkOejk'] ``` ### API key authentication If your Elasticsearch cluster is protected by API keys, you must provide the key and secret in authentication configuration to connect Ibexa DXP with the cluster. With API key authentication you can define different authorization levels, such as [`create_index` or `index`](https://www.elastic.co/guide/en/elasticsearch/reference/8.19/security-privileges.html#privileges-list-indices). Such approach proves useful if the cluster is available to the public. For more information, see [Create API key](https://www.elastic.co/guide/en/elasticsearch/reference/8.19/security-api-create-api-key.html). When using API key authentication, you must pass the following parameters to authenticate access to the cluster: ``` : # ... authentication: type: api_key credentials: ['', ''] ``` For example: ``` ibexa_elasticsearch: connections: cloud: debug: true elastic_cloud_id: 'test:ZWFzdHVzMi5henVyZS5lbGFzdGljLWNsb3VkLmNvbTo5MjQzJGUwZ' authentication: type: api_key credentials: ['ui2lp2axTNmsyakw9tvNnw', 'VuaCfGcBCdbkQm-e5aOx'] ``` Alternatively, pass the encoded API key value, which Elasticsearch also calls "API key credentials": ``` : # ... authentication: type: api_key credentials: [''] ``` For example: ``` ibexa_elasticsearch: connections: cloud: debug: true elastic_cloud_id: 'test:ZWFzdHVzMi5henVyZS5lbGFzdGljLWNsb3VkLmNvbTo5MjQzJGUwZ' authentication: type: api_key credentials: ['VnVhQ2ZHY0JDZGJrUW0tZTVhT3g6dWkybHAyYXhUTm1zeWFrdzl0dk5udw=='] ``` To see the difference between API key, API key id, and encoded API key, refer to the [examples in Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/8.19/security-api-create-api-key.html#security-api-create-api-key-example). ### SSL When you need to protect your communication with the Elasticsearch server, you can use SSL encryption. When configuring SSL for your internal infrastructure, you can use your own client certificates signed by a public CA. Configure SSL by passing the path-passwords pairs for both the certificate and the certificate key. For example: ``` ibexa_elasticsearch: connections: cloud_with_ssl: debug: true elastic_cloud_id: 'test:ZWFzdHVzMi5henVyZS5lbGFzdGljLWNsb3VkLmNvbTo5MjQzJGUwZ' authentication: type: api_key credentials: ['8Ek5f3IBGQlWj6v4M7zG', 'rmI6IechSnSJymWJ4LZqUw'] ssl: cert: path: '/path/to/cert.pem' pass: ~ cert_key: path: '/path/to/cert-key' pass: ~ ``` If you don't have a client certificate signed by public certificate authority, but you have a self-signed CA certificate generated by `elasticsearch-certutil` or another tool (for example for development purposes), use the following `ssl` configuration: ``` ibexa_elasticsearch: connections: cloud_with_ssl: debug: true elastic_cloud_id: 'test:ZWFzdHVzMi5henVyZS5lbGFzdGljLWNsb3VkLmNvbTo5MjQzJGUwZ' ssl: ca_cert: path: '/path/to/ca_cert.pem' ``` If you configure both `ca_cert` and `cert` entries, the `ca_cert` parameter takes precedence over the `cert` parameter. After you have configured SSL, you can still disable it, for example when the certificates expire, or you're migrating to a new set of certificates. To do this, pass the following setting under the `ssl` key: ``` verification: false ``` For more information, see [Elasticsearch: SSL Encryption](https://www.elastic.co/guide/en/elasticsearch/client/php-api/8.19/connecting.html#ssl-encryption). ### Enable debugging In a staging environment, you can log messages about the status of communication with Elasticsearch. You can then use Symfony Profiler to review the logs. By default, debugging is disabled. To enable debugging, you can use the following setting: ``` : # ... debug: ``` - `debug` logs information about requests, including request status and timing > **Note: Elasticsearch 7 compatibility** > > If you're using Elasticsearch 7, you can also use the `trace` setting for additional debugging information. This setting is deprecated and removed in Elasticsearch 8. > **Tip: Tip** > > Make sure that you disable debugging in a production environment. ## Define field type mapping templates Before you can re-index the Ibexa DXP data, so that Elasticsearch can search through its contents, you must define an index template. Templates instruct Elasticsearch to recognize Ibexa DXP fields as specific data types, based on, for example, a field name. They help you prevent Elasticsearch from using the dynamic field mapping feature to create type mappings automatically. You can create several field type mapping templates for each index, for example, to define settings that are specific for different languages. When you establish a relationship between a field mapping template and a connection, you can apply several templates, too. ### Define a template To define a field mapping template, you must provide settings under the `index_templates` key. The structure of the template is as follows: ``` ibexa_elasticsearch: # ... index_templates: : patterns: # ... settings: # ... mappings: # ... ``` Set a unique name for the template and configure the following keys: - `patterns` - A list of wildcards that Elasticsearch uses to match the field mapping template to an index. Index names use the following pattern: `___` By default, repository name is set to `default`, however, in the context of an Ibexa DXP instance, there can be [several repositories with different names](https://doc.ibexa.co/en/latest/administration/configuration/repository_configuration/#defining-custom-connection). Document type can be either `content` or `location`. In a language code, hyphens are replaced with underscores, and all characters must be lowercase. An index name can therefore look like this: `default_content_eng_gb_2` You can use the `patterns` setting when your data contains content in different languages. You can create index templates with settings that apply to a specific language only, for example, to eliminate stop words from the index, or help divide concatenations. You use patterns to identify index templates that contain settings specific for a given language: ``` ibexa_elasticsearch: # ... index_templates: default_en_us: patterns: ['default_*', '*eng_us*'] # ... ``` - `settings` - Settings under this key control all aspects related to an index. For more information and a list of available settings, see [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/8.19/index-modules.html#index-modules-settings). ``` For example, you can define settings that convert text into a format that is optimized for search, like a normalizer that changes a case of all phrases in the index: ``` ``` ibexa_elasticsearch: # ... index_templates: default: # ... settings: analysis: normalizer: lowercase_normalizer: type: custom char_filter: [] filter: lowercase # ... ``` - `mappings` - Settings under this key define mapping for fields in the index. For more information about mappings, see [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/8.19/mapping.html). ``` When you create a custom index template, with settings for your own field and document types, make sure that it contains mappings for all searchable fields that are available in Ibexa DXP. ``` To see the default configuration, go to `vendor/ibexa/elasticsearch/src/bundle/Resources/config/` and open the `default-config.yaml` file. ### Fine-tune the search results Your search results can be adjusted by configuring additional parameters. For a list of available mapping parameters and their usage, see [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/8.19/mapping-params.html). For example, you can apply a mapping parameter, in this case, a normalizer, to a specific mapping under the `dynamic_templates` key: ``` ibexa_elasticsearch: # ... index_templates: default: # ... mappings: # ... dynamic_templates: - ez_string: match: "*_s" mapping: type: keyword normalizer: lowercase_normalizer # ... ``` You can also set a boosting factor for a specific field. Boosting increases the relevance of hits, for example making keywords from the title more relevant than the ones from other places of the document. Set the boosting factor under the `properties` key: ``` ibexa_elasticsearch: # ... index_templates: default: # ... mappings: properties: content_name_s: boost: 2.0 # ... ``` You can even copy contents of existing fields, process them and then paste into another field, which than can be queried: ``` ibexa_elasticsearch: # ... index_templates: default: # ... mappings: properties: user_first_name_s: type: keyword normalizer: lowercase_normalizer copy_to: custom_field # ... ``` ### Add language-specific analysers You can configure Elasticsearch to perform language-specific analysis like stemming. This way searching for "cars" returns hits with content that contains the word "car". On a multilingual site, you can have different analyzers configured for different languages, something which is typically required because stemming rules are language-specific. #### Make a copy of the default template To enable a language-specific analyzer, create a new template for each language in `config/packages/ibexa_elasticsearch.yaml` first. This template should be based on the `default` template found in `vendor/ibexa/elasticsearch/src/bundle/Resources/config/default-config.yaml`. The name of the new template should indicate the language it applies to, for example `eng_gb`, `nor_no` or `fre_fr`. #### Change match pattern for the new template The default template matches on `*_location_*` and `*_content_*`. These patterns aren't language-specific and you cannot use them if you plan to use different templates for different languages. In your copy of the default template, change the pattern as follows: ``` patterns: - - '*_location_*' - - '*_content_*' + - "*_eng_gb*" ``` This pattern matches on English. For more information about specifying the pattern for your language, see [Define a template](#define-a-template). #### Create config for language specific analyzer For information about configuring an analyzer for each specific language, see [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/8.19/analysis-lang-analyzer.html). An adoption of the [`english` analyzer](https://www.elastic.co/guide/en/elasticsearch/reference/8.19/analysis-lang-analyzer.html#english-analyzer) in Ibexa DXP configuration looks like this: ``` ibexa_elasticsearch: index_templates: english: patterns: - '*_eng_gb*' settings: analysis: normalizer: lowercase_normalizer: type: custom char_filter: [] filter: - lowercase analyzer: english_analyzer: type: custom tokenizer: lowercase filter: - lowercase - english_stop - english_keywords - english_stemmer - english_possessive_stemmer ibexa_spellcheck_analyzer: type: custom tokenizer: lowercase filter: - lowercase - ibexa_spellcheck_shingle_filter ibexa_spellcheck_raw_analyzer: type: custom tokenizer: standard filter: - lowercase - english_possessive_stemmer filter: ibexa_spellcheck_shingle_filter: type: shingle min_shingle_size: 2 max_shingle_size: 3 english_stop: type: stop stopwords: '_english_' english_keywords: type: keyword_marker keywords: [] english_stemmer: type: stemmer language: light_english english_possessive_stemmer: type: stemmer language: possessive_english refresh_interval: "-1" mappings: dynamic_templates: - ez_int: match: "*_i" mapping: type: integer - ez_mint: match: "*_mi" mapping: type: integer - ez_id: match: "*_id" mapping: type: keyword - ez_mid: match: "*_mid" mapping: type: keyword - ez_string: match: "*_s" mapping: type: keyword normalizer: lowercase_normalizer - ez_mstring: match: "*_ms" mapping: type: keyword normalizer: lowercase_normalizer - ez_long: match: "*_l" mapping: type: long - ez_mlong: match: "*_ml" mapping: type: long - ez_text: match: "*_t" mapping: type: text analyzer: english_analyzer - ez_text_fulltext: match: "*_fulltext" mapping: type: text analyzer: english_analyzer - ez_boolean: match: "*_b" mapping: type: boolean - ez_mboolean: match: "*_mb" mapping: type: boolean - ez_float: match: "*_f" mapping: type: float - ez_double: match: "*_d" mapping: type: double - ez_date: match: "*_dt" mapping: type: date - ez_geolocation: match: "*_gl" mapping: type: geo_point - ez_spellcheck: match: "*_spellcheck" mapping: type: text analyzer: ibexa_spellcheck_analyzer fields: raw: type: text analyzer: ibexa_spellcheck_raw_analyzer ``` Then, you must bind this language template to your Elasticsearch connection. ## Bind templates with connections After you create an index template (for example, for specific data types or linguistic analysis), you must link it to an Elasticsearch connection by adding the `index_templates` key to the connection definition. If your configuration file contains several connection definitions, you can reuse the same template for different connections. If you have several index templates, you can apply different combinations of templates to different connections. ``` ibexa_elasticsearch: connections: : # ... index_templates: - eng_gb : # ... index_templates: - eng_gb - fre_fr - ger_de ``` For more information about how Elasticsearch handles settings and mappings from multiple templates that match the same index, see [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/8.19/index-templates.html). # Extend Elasticsearch To learn how you can create document field mappers, custom Search Criteria, custom Sort Clauses and Aggregations, see [Create custom Search Criterion](https://doc.ibexa.co/en/latest/search/extensibility/create_custom_search_criterion/index.md). # Solr search engine [Solr search engine](https://github.com/ibexa/solr) allows you to use advanced search features: filtering, query, full-text search, and aggregations. When you enable Solr and re-index your content, all your existing Search queries by using `SearchService` are powered by Solr automatically. This allows you to scale up your Ibexa DXP installation and be able to continue development locally against SQL engine, and have a test infrastructure, Staging, and Prod powered by Solr. By this, it also removes considerable load from your database. For more information on the architecture of Ibexa DXP, see [Architecture](https://doc.ibexa.co/en/latest/administration/project_organization/architecture/index.md). # Install Solr search engine ## Configure and start Solr The example presents a configuration with a single core. For configuring Solr in other ways, including examples, see [Solr Cores and `solr.xml`](https://solr.apache.org/guide/7_7/solr-cores-and-solr-xml.html) and [core administration](https://cwiki.apache.org/confluence/display/solr/CoreAdmin). ### Download Solr files > **Note: Solr versions** > > Supported Solr versions are Solr 8 and 9. Using the most recent version of Solr is recommended. Download and extract Solr: - [solr-8.11.2.tgz](https://www.apache.org/dyn/closer.lua/lucene/solr/8.11.2/solr-8.11.2.tgz) or [solr-8.11.2.zip](https://www.apache.org/dyn/closer.lua/lucene/solr/8.11.2/solr-8.11.2.zip) - [solr-9.8.1.tgz](https://archive.apache.org/dist/solr/solr/9.8.1/solr-9.8.1.tgz) Copy the necessary configuration files. The examples below copy from the root of your DXP project to the place you've extracted Solr: **Solr 9** Ibexa DXP provides the following required configuration files: - `vendor/ibexa/solr/src/lib/Resources/config/solr/solr.languages` directory - `vendor/ibexa/solr/src/lib/Resources/config/solr/managed-schema.xml` - `vendor/ibexa/solr/src/lib/Resources/config/solr/custom-fields-types-solr9.xml` - `vendor/ibexa/solr/src/lib/Resources/config/solr/language-fieldtypes.xml` ``` # Make sure to replace the /opt/solr/ path with where you have placed Solr cd /opt/solr mkdir -p server/ibexa/template/conf cp -R /vendor/ibexa/solr/src/lib/Resources/config/solr/solr.languages server/ibexa/template/conf cp -R /vendor/ibexa/solr/src/lib/Resources/config/solr/{managed-schema.xml,custom-fields-types-solr9.xml,language-fieldtypes.xml} server/ibexa/template/conf cp server/solr/configsets/_default/conf/{solrconfig.xml,stopwords.txt,synonyms.txt} server/ibexa/template/conf cp server/solr/solr.xml server/ibexa # Modify solrconfig.xml to remove the section that doesn't agree with your schema sed -i.bak '//d' server/ibexa/template/conf/solrconfig.xml # Start Solr (but apply autocommit settings below first if you need to) # The configuration path is an absolute path bin/solr -s ibexa bin/solr create_core -c collection1 -d /opt/solr/server/ibexa/template ``` **Solr 8** Ibexa DXP provides the following required configuration files: - `vendor/ibexa/solr/src/lib/Resources/config/solr/solr.languages` directory - `vendor/ibexa/solr/src/lib/Resources/config/solr/schema.xml` - `vendor/ibexa/solr/src/lib/Resources/config/solr/custom-fields-types.xml` - `vendor/ibexa/solr/src/lib/Resources/config/solr/language-fieldtypes.xml` ``` # Make sure to replace the /opt/solr/ path with where you have placed Solr cd /opt/solr mkdir -p server/ibexa/template cp -R /vendor/ibexa/solr/src/lib/Resources/config/solr/solr.languages server/ibexa/template cp -R /vendor/ibexa/solr/src/lib/Resources/config/solr/{schema.xml,custom-fields-types.xml,language-fieldtypes.xml} server/ibexa/template cp server/solr/configsets/_default/conf/{solrconfig.xml,stopwords.txt,synonyms.txt} server/ibexa/template cp server/solr/solr.xml server/ibexa # Modify solrconfig.xml to remove the section that doesn't agree with your schema sed -i.bak '//d' server/ibexa/template/solrconfig.xml # Start Solr (but apply autocommit settings below first if you need to) bin/solr -s ibexa bin/solr create_core -c collection1 -d server/ibexa/template ``` #### Set up SolrCloud SolrCloud is a cluster of Solr servers. It enables you to: - centralize configuration - automatically load balance and fail-over for queries - integrate ZooKeeper for cluster coordination and configuration To set SolrCloud up follow [SolrCloud reference guide](https://solr.apache.org/guide/7_7/solrcloud.html). ### Continue Solr configuration #### Configure commit frequency The bundle doesn't commit Solr index changes directly on repository updates, leaving it up to you to tune this using `solrconfig.xml` as best practice suggests. This setting is **required** if you want to see the changes after publish. It's strongly recommended to set-up `solrconfig.xml` like this: ``` ${solr.autoCommit.maxTime:15000} false ${solr.autoSoftCommit.maxTime:20} ``` #### Configure spellcheck Configure the spellcheck component in `solrconfig.xml`: ``` default meta_content__text_t solr.DirectSolrSpellChecker internal 0.5 2 1 5 4 0.01 ``` Add this `spellcheck` component to the `/select` request handler: ``` spellcheck ``` ### Generate Solr configuration automatically The command line tool `bin/generate-solr-config.sh` generates Solr configuration automatically. It can be used for deploying to Ibexa Cloud (Upsun) and on-premise installs. Execute the script from the Ibexa DXP root directory for further information: ``` ./vendor/ibexa/solr/bin/generate-solr-config.sh --help ``` ## Configure the bundle The Solr Search Engine Bundle can be configured in many ways. The config further below assumes you have parameters set up for Solr DSN and search engine *(however both are optional)*, for example: ``` env(SEARCH_ENGINE): solr env(SOLR_DSN): 'http://localhost:8983/solr' env(SOLR_CORE): collection1 ``` ### Configure Solr version When using Solr 9, it's required to set the `version` parameter with the Solr version. The parameter is optional when using lower Solr versions. ``` ibexa_solr: version: '9.8.1' ``` ### Single-core example (default) Out of the box in Ibexa DXP the following is enabled for a setup: ``` ibexa_solr: endpoints: endpoint0: dsn: '%env(string:SOLR_DSN)%' core: '%env(string:SOLR_CORE)%' connections: default: entry_endpoints: - endpoint0 mapping: default: endpoint0 ``` ### Shared-core example The following example separates one language. The installation contains several similar languages, and one different language that should receive proper language analysis for proper stemming and sorting behavior by Solr: ``` ibexa_solr: endpoints: endpoint0: dsn: '%env(string:SOLR_DSN)%' core: core0 endpoint1: dsn: '%env(string:SOLR_DSN)%' core: core1 connections: default: entry_endpoints: - endpoint0 - endpoint1 mapping: translations: jpn-JP: endpoint1 # Other languages, for instance eng-US and other western languages are sharing core default: endpoint0 ``` ### Multi-core example If full language analysis features are preferred, then each language can be configured with separate cores. > **Note: Note** > > Make sure to test this setup against a single-core setup, as it might perform worse than single-core if your project uses a lot of language fallbacks per SiteAccess, as queries are then performed across several cores at once. ``` ibexa_solr: version: '9.8.1' # Required only if using Solr 9 endpoints: endpoint0: dsn: '%env(string:SOLR_DSN)%' core: core0 endpoint1: dsn: '%env(string:SOLR_DSN)%' core: core1 endpoint2: dsn: '%env(string:SOLR_DSN)%' core: core2 endpoint3: dsn: '%env(string:SOLR_DSN)%' core: core3 endpoint4: dsn: '%env(string:SOLR_DSN)%' core: core4 endpoint5: dsn: '%env(string:SOLR_DSN)%' core: core5 endpoint6: dsn: '%env(string:SOLR_DSN)%' core: core6 connections: default: entry_endpoints: - endpoint0 - endpoint1 - endpoint2 - endpoint3 - endpoint4 - endpoint5 - endpoint6 mapping: translations: jpn-JP: endpoint1 eng-US: endpoint2 fre-FR: endpoint3 ger-DE: endpoint4 esp-ES: endpoint5 # Not really used, but specified here for fallback if more languages are suddenly added by content admins default: endpoint0 # Also use separate core for main languages (differs from content object to content object) # This is useful to reduce number of cores queried for always available language fallbacks main_translations: endpoint6 ``` ### SolrCloud example To use SolrCloud you need to specify data distribution strategy for connection via the `distribution_strategy` option to [`cloud`](https://solr.apache.org/guide/7_7/solrcloud.html). The example is based on multi-core setup so any specific language analysis options could be specified on the collection level. ``` ibexa_solr: endpoints: main: dsn: '%env(string:SOLR_DSN)%' core: '%solr_main_core%' en: dsn: '%env(string:SOLR_DSN)%' core: '%solr_en_core%' fr: dsn: '%env(string:SOLR_DSN)%' core: '%solr_fr_core%' # ... connections: default: distribution_strategy: cloud entry_endpoints: - main - en - fr # - ... mapping: translations: eng-GB: en fre-FR: fr # ... main_translations: main ``` This solution uses the default SolrCloud [document routing strategy: `compositeId`](https://solr.apache.org/guide/7_7/shards-and-indexing-data-in-solrcloud.html#document-routing). ### Solr Basic HTTP Authorization Solr core can be secured with Basic HTTP Authorization. For more information, see [Solr Basic Authentication Plugin](https://solr.apache.org/guide/7_7/basic-authentication-plugin.html). In the example below we configured Solr Bundle to work with secured Solr core. ``` ibexa_solr: endpoints: endpoint0: dsn: '%env(string:SOLR_DSN)%' core: core0 user: example pass: password ``` Obviously, you should pass credentials for every configured and HTTP Basic secured Solr core. Configuration for multi core setup is exactly the same. ## Configure repository with the specific search engine The following is an example of configuring Solr search engine, where `connection` name is same as in the example above, and engine is set to `solr`: ``` ibexa: repositories: default: storage: ~ search: engine: '%search_engine%' connection: default ``` `%search_engine%` is a parameter that is configured in `config/packages/ibexa.yaml`, and should be changed from its default value `legacy` to `solr` to activate Solr as the search engine. ## Clear prod cache While Symfony `dev` environment keeps track of changes to YAML files, `prod` doesn't, so clear the cache to make sure Symfony reads the new config: ``` php bin/console --env=prod cache:clear ``` ## Run CLI indexing command The last step is to execute the initial indexation of data: ``` php bin/console --env=prod --siteaccess= ibexa:reindex ``` ### Possible exceptions If you haven't configured your setup correctly, some exceptions might happen on indexing. Here are the most common issues you may encounter: - Exception if Binary files in database have an invalid path prefix - Make sure `var_dir` is configured properly in `ibexa.yaml` configuration. - If your database is inconsistent in regards to file paths, try to update entries to be correct *(make sure to make a backup first)*. - Exception on unsupported field types - Make sure to implement all field types in your installation, or to configure missing ones as [NullType](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/nullfield/index.md) if implementation isn't needed. - Content isn't immediately available - Solr Bundle on purpose doesn't commit changes directly on Repository updates *(on indexing)*, but lets you control this using Solr configuration. Adjust Solr's `autoSoftCommit` (visibility of changes to search index) and/or `autoCommit` (hard commit, for durability and replication) to balance performance and load on your Solr instance against needs you have for "[NRT](https://solr.apache.org/guide/7_7/near-real-time-searching.html)". - Running out of memory during indexing - In general make sure to run indexing using the prod environment to avoid debuggers and loggers from filling up memory. # Configure Solr ## Configure boosting > **Caution: Index time boosting** > > Index time boosting was deprecated in Solr 6.5 and removed in Solr 7.0. Until query time boosting is implemented, there is no way to boost in the bundle out of the box. > **Tip: How boosting interacts with Search API** > > Boosting of fields or documents affects the score (relevance) of your search result hits when using Search API for any Criteria you specify on `$query->query`, or in REST by using `Query` element. When you don't specify anything to sort on, the result is sorted by this relevance. Anything set on `$query->filter`, or in REST by using `Filter` element, *doesn't* affect scoring and only works as a pure filter for the result. Thus make sure to place Criteria you want to affect scoring on `query`. Boosting currently happens when indexing, so if you change your configuration you need to re-index. Boosting tells the search engine which parts of the content model have more importance when searching, and is an important part of tuning your search results relevance. Importance is defined by using a numeric value, where `1.0` is default, values higher than that are more important, and values lower (down to `0.0`) are less important. Boosting is configured per connection that you configure to use for a given repository, like in this `config/packages/ibexa_solr.yaml` example: ``` ibexa_solr: connections: default: boost_factors: content_type: # Boost a whole content type article: 2.0 meta_field: # Boost a meta Field (name, text) system wide, or for a given content type name: 10.0 article: # Boost the meta full text Field for article more than 2.0 set above text: 5.0 ``` The configuration above results in the following boosting (content type / Field): - `article/title: 2.0` - `news/description: 1.0` (default) - `article/text (meta): 5.0` - `blog_post/name (meta): 10.0` - `article/name (meta): 2.0` > **Tip: How to configure boosting on specific fields** > > Currently, boosting on particular fields is missing. However, it could be configured using 3rd party [Novactive/NovaeZSolrSearchExtraBundle](https://github.com/Novactive/NovaeZSolrSearchExtraBundle) in case of custom search implementation, for example, to handle your front-end search form. Unfortunately, this doesn't affect search performed in the administration interface. > > The following example presents boosting configuration for Folder's `name` and `description` fields. First, in `ibexa_solr.yaml` configure [custom fulltext fields](https://github.com/Novactive/NovaeZSolrSearchExtraBundle/blob/master/doc/custom_fields.md). > > ``` > ez_solr_search_extra: > system: > default: > fulltext_fields: > custom_folder_name: > - folder/name > custom_folder_description: > - folder/description > ``` > > The second step requires you to use `\Novactive\EzSolrSearchExtra\Query\Content\Criterion\MultipleFieldsFullText` instead of default `\Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion\FullText`. The following example shows custom query which benefits from the custom fields created in the previous example. > > ``` > > namespace App\Controller; > > use Ibexa\Contracts\Core\Repository\SearchService; > use Ibexa\Contracts\Core\Repository\Values\Content\Query; > use Symfony\Component\HttpFoundation\Request; > use Symfony\Component\HttpFoundation\Response; > > class SearchController > { > /** > * @var \Ibexa\Contracts\Core\Repository\SearchService > */ > private $searchService; > > public function __construct(SearchService $searchService) > { > $this->searchService = $searchService; > } > > public function searchAction(Request $request): Response > { > $queryString = $request->get('query'); > > $query = new Query(); > $query->query = new \Novactive\EzSolrSearchExtra\Query\Content\Criterion\MultipleFieldsFullText( > $queryString, > [ > 'metaBoost' => [ > 'custom_folder_name' => 20.0, > 'custom_folder_description' => 10.0, > ] > ] > ); > > $searchResult = $this->searchService->findContent($query); > > ... > } > } > ``` > > Remember to clear the cache and perform search engine reindex afterwords. > > The above configuration results in the following boosting (content type / field): > > - `folder/name: 20.0` > - `folder/description: 10.0` ## Index related objects You can use indexation of related objects to search through text of related content. Indexing is disabled by default. To set it up you need to define the maximum indexing depth using the following YAML configuration: ``` ibexa_solr: # ... connections: default: # ... indexing_depth: # Default value: 0 - no relation indexing, 1 - direct relations, 2nd level relations, 3rd level relations (maximum value). default: 1 content_type: # Index depth defined for specific content type article: 2 ``` ## Configure Solr Replication (master/slave) > **Note: Note** > > The configuration below has been tested on Solr 7.7. ### Configure Master for replication First you need to change the core configuration in `solrconfig.xml` (for example `*/opt/solr/server/ibexa/collection1/conf/solrconfig.xml`). You can copy and paste the code below before any other `requestHandler` section. ``` optimize optimize schema.xml,stopwords.txt,elevate.xml 00:00:10 2 16 solrconfig_slave.xml:solrconfig.xml,x.xml,y.xml ``` Then restart the master with: ``` sudo su - solr -c "/opt/solr/bin/solr restart" ``` ### Configure Slave for replication You have to edit the same file on the slave server, and use the code below: ``` http://123.456.789.0:8983/solr/collection1/replication 00:00:20 internal 5000 10000 username password ``` Next, restart Solr slave. Connect to the Solr slave interface (), go to your core and check the replication status: *[Image: Solr Slave]* # Configure HTTP Client for Solr queries Ibexa Solr Bundle uses Symfony HTTP Client to fetch and update Solr index. You can configure timeout and maximum number of retries for that client using Solr Bundle's Semantic configuration: ``` ibexa_solr: # ... http_client: # ... timeout: 30 max_retries: 5 ``` # Extend Solr To learn how you can create document field mappers, custom Search Criteria, custom Sort Clauses and Aggregations, see [Search extensibility](https://doc.ibexa.co/en/latest/search/extensibility/create_custom_search_criterion/index.md). # Legacy search engine Legacy search engine is the default search engine. It's SQL-based and uses Doctrine's database connection. The connections are defined in the same way as for storage engine, and no further specific configuration is needed. Legacy search engine is recommended for basic needs and isn't intended in production. It allows you to use filtering and full-text search, but with some limitations. For more information, check [search engine comparison](https://doc.ibexa.co/en/latest/search/search_engines/search_engines/#search-engines-comparison) > **Tip: Tip** > > The features and performance of Legacy search engine are limited. If you have specific search or performance needs, it's recommended to use [Solr](https://doc.ibexa.co/en/latest/search/search_engines/solr_search_engine/solr_overview/index.md) or [Elasticsearch](https://doc.ibexa.co/en/latest/search/search_engines/elasticsearch/elasticsearch_overview/index.md) instead. > > Using the Legacy search engine disables most shop features, such as product search. # Configure repository with Legacy search engine Search can be configured independently from storage, and the following configuration example shows both the default values, and how you configure legacy as the search engine: ``` ibexa: repositories: main: storage: engine: legacy connection: default search: engine: legacy connection: default ``` # Search API You can search for content with the PHP API in two ways. To do this, you can use the [`SearchService`](#searchservice) or [Repository filtering](#repository-filtering). ## SearchService [`SearchService`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-SearchService.html) enables you to perform search queries by using the PHP API. The service should be [injected into the constructor of your command or controller](https://doc.ibexa.co/en/latest/api/php_api/php_api/#service-container). > **Tip: SearchService in the back office** > > `SearchService` is also used in the back office of Ibexa DXP, in components such as Universal Discovery Widget or Sub-items List. ### Perform search To search through content you need to create a [`LocationQuery`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-LocationQuery.html) and provide your Search Criteria as a series of Criterion objects. For example, to search for all content of a selected content type, use one Criterion, [`Criterion\ContentTypeIdentifier`](https://doc.ibexa.co/en/latest/search/criteria_reference/contenttypeidentifier_criterion/index.md) (line 14). The following command takes the content type identifier as an argument and lists all results: ``` // ... use Ibexa\Contracts\Core\Repository\SearchService; use Ibexa\Contracts\Core\Repository\Values\Content\LocationQuery; use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion; // ... class FindContentCommand extends Command { // ... protected function execute(InputInterface $input, OutputInterface $output): int { $contentTypeIdentifier = $input->getArgument('contentTypeIdentifier'); $query = new LocationQuery(); $query->filter = new Criterion\ContentTypeIdentifier($contentTypeIdentifier); $result = $this->searchService->findContentInfo($query); $output->writeln('Found ' . $result->totalCount . ' items'); foreach ($result->searchHits as $searchHit) { $output->writeln($searchHit->valueObject->name); } return self::SUCCESS; } } ``` [`SearchService::findContentInfo`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-SearchService.html#method_findContentInfo) (line 16) retrieves [`ContentInfo`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Persistence-Content-ContentInfo.html) objects of the found content items. You can also use [`SearchService::findContent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-SearchService.html#method_findContent) to get full Content objects, together with their field information. To query for a single result, for example by providing a Content ID, use the [`SearchService::findSingle`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-SearchService.html#method_findSingle) method: ``` $criterion = new Criterion\ContentId($contentId); $result = $this->searchService->findSingle($criterion); $output->writeln($result->getName()); ``` > **Tip: Tip** > > For full list and details of available Search Criteria, see [Search Criteria reference](https://doc.ibexa.co/en/latest/search/criteria_reference/search_criteria_reference/index.md). > **Note: Search result limit** > > By default search returns up to 25 results. You can change it by setting a different limit to the query: > > ``` > $query->limit = 100; > ``` #### Search with `query` and `filter` You can use two properties of the `Query` object to search for content: `query` and `filter`. In contrast to `filter`, `query` has an effect of search scoring (relevancy). It affects default sorting if no Sort Clause is used. As such, `query` is recommended when the search is based on user input. The difference between `query` and `filter` is only relevant when using Solr or Elasticsearch search engine. With the Legacy search engine both properties give identical results. #### Process large result sets To process a large result set, use [`Ibexa\Contracts\Core\Repository\Iterator\BatchIterator`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Iterator-BatchIterator.html). `BatchIterator` divides the results of search or filtering into smaller batches. This enables iterating over results that are too large to handle due to memory constraints. `BatchIterator` takes one of the available adapters ([`\Ibexa\Contracts\Core\Repository\Iterator\BatchIteratorAdapter`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/namespaces/ibexa-contracts-core-repository-iterator-batchiteratoradapter.html)) and optional batch size. For example: ``` $query = new LocationQuery(); $iterator = new BatchIterator(new BatchIteratorAdapter\LocationSearchAdapter($this->searchService, $query)); foreach ($iterator as $result) { $output->writeln($result->valueObject->getContentInfo()->name); } ``` You can also define the batch size by setting `$iterator->setBatchSize()`. The following BatchIterator adapters are available, for both `query` and `filter` searches: | Adapter | Method | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [`ContentFilteringAdapter`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Iterator-BatchIteratorAdapter-ContentFilteringAdapter.html) | [`Ibexa\Contracts\Core\Repository\ContentService::find`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-ContentService.html#method_find) | | [`ContentInfoSearchAdapter`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Iterator-BatchIteratorAdapter-ContentInfoSearchAdapter.html) | [`Ibexa\Contracts\Core\Repository\SearchService::findContentInfo`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-SearchService.html#method_findContentInfo) | | [`ContentSearchAdapter`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Iterator-BatchIteratorAdapter-ContentSearchAdapter.html) | [`Ibexa\Contracts\Core\Repository\SearchService::findContent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-SearchService.html#method_findContent) | | [`LocationFilteringAdapter`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Iterator-BatchIteratorAdapter-LocationFilteringAdapter.html) | [`Ibexa\Contracts\Core\Repository\LocationService::find`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-LocationService.html#method_find) | | [`LocationSearchAdapter`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Iterator-BatchIteratorAdapter-LocationSearchAdapter.html) | [`Ibexa\Contracts\Core\Repository\SearchService::findLocations`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-SearchService.html#method_findLocations) | | [`AttributeDefinitionFetchAdapter`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Iterator-BatchIteratorAdapter-AttributeDefinitionFetchAdapter.html) | [`Ibexa\Contracts\ProductCatalog\AttributeDefinitionServiceInterface::findAttributesDefinitions`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-AttributeDefinitionServiceInterface.html#method_findAttributesDefinitions) | | [`AttributeGroupFetchAdapter`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Iterator-BatchIteratorAdapter-AttributeGroupFetchAdapter.html) | [`Ibexa\Contracts\ProductCatalog\AttributeGroupServiceInterface::findAttributeGroups`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-AttributeGroupServiceInterface.html#method_findAttributeGroups) | | [`CurrencyFetchAdapter`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Iterator-BatchIteratorAdapter-CurrencyFetchAdapter.html) | [`Ibexa\Contracts\ProductCatalog\CurrencyServiceInterface::findCurrencies`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-CurrencyServiceInterface.html#method_findCurrencies) | | [`ProductTypeListAdapter`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Iterator-BatchIteratorAdapter-ProductTypeListAdapter.html) | [`Ibexa\Contracts\ProductCatalog\ProductTypeServiceInterface::findProductTypes`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-ProductTypeServiceInterface.html#method_findProductTypes) | ## Repository filtering You can use the `ContentService::find(Filter)` method to find content items or `LocationService::find(Filter)` to find locations by using a defined Filter. `ContentService::find` returns an iterable [`ContentList`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-ContentList.html) while `LocationService::find` returns an iterable [`LocationList`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-LocationList.html). Filtering differs from search. It doesn't use the `SearchService` and isn't based on indexed data. [`Filter`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Filter-Filter.html) enables you to configure a query by using chained methods to select criteria, sorting, limit, and offset. For example, the following command lists all content items under the specified parent location and sorts them by name in descending order: ``` // ... use Ibexa\Contracts\Core\Repository\ContentService; use Ibexa\Contracts\Core\Repository\Values\Content\Query; use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion; use Ibexa\Contracts\Core\Repository\Values\Content\Query\SortClause; use Ibexa\Contracts\Core\Repository\Values\Filter\Filter; // ... class FilterCommand extends Command { protected function execute(InputInterface $input, OutputInterface $output): int { $parentLocationId = (int)$input->getArgument('parentLocationId'); $filter = new Filter(); $filter ->withCriterion(new Criterion\ParentLocationId($parentLocationId)) ->withSortClause(new SortClause\ContentName(Query::SORT_DESC)); $result = $this->contentService->find($filter, []); $output->writeln('Found ' . $result->getTotalCount() . ' items'); foreach ($result as $content) { $output->writeln($content->getName() ?? 'No content name'); } return self::SUCCESS; } } ``` The same Filter can be applied to find locations instead of content items, for example: ``` // ... use Ibexa\Contracts\Core\Repository\LocationService; use Ibexa\Contracts\Core\Repository\Values\Content\Query; use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion; use Ibexa\Contracts\Core\Repository\Values\Content\Query\SortClause; use Ibexa\Contracts\Core\Repository\Values\Filter\Filter; class FilterCommand extends Command { // ... protected function execute(InputInterface $input, OutputInterface $output): int { $parentLocationId = (int)$input->getArgument('parentLocationId'); $filter = new Filter(); $filter ->withCriterion(new Criterion\ParentLocationId($parentLocationId)) ->withSortClause(new SortClause\ContentName(Query::SORT_DESC)); $result = $this->locationService->find($filter, []); $output->writeln('Found ' . $result->getTotalCount() . ' items'); foreach ($result as $content) { $output->writeln($content->getContent()->getName()); } return self::SUCCESS; } } ``` > **Caution: Caution** > > The total count is the total number of matched items, regardless of pagination settings. > **Tip: Repository filtering is SiteAccess-aware** > > Repository filtering is SiteAccess-aware, which means you can skip the second argument of the `find` methods. In that case languages from a current context are injected and added as a LanguageCode Criterion filter. You can use the following methods of the Filter: - `withCriterion` - add the first Criterion to the Filter - `andWithCriterion` - add another Criterion to the Filter using a LogicalAnd operation. If this is the first Criterion, this method works like `withCriterion` - `orWithCriterion` - add a Criterion using a LogicalOr operation. If this is the first Criterion, this method works like `withCriterion` - `withSortClause` - add a Sort Clause to the Filter - `sliceBy` - set limit and offset for pagination - `reset` - remove all Criteria, Sort Clauses, and pagination settings The following example filters for Folder content items under the parent location 2, sorts them by publication date and returns 10 results, starting from the third one: ``` $filter = new Filter(); $filter ->withCriterion(new Criterion\ContentTypeIdentifier('folder')) ->andWithCriterion(new Criterion\ParentLocationId(2)) ->withSortClause(new SortClause\DatePublished(Query::SORT_ASC)) ->sliceBy(10, 2); ``` > **Note: Search Criteria and Sort Clause availability** > > Not all Search Criteria and Sort Clauses are available for use in repository filtering. > > Only Criteria implementing [`FilteringCriterion`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Filter-FilteringCriterion.html) and Sort Clauses implementing [`FilteringSortClause`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Filter-FilteringSortClause.html) are supported. > > See [Search Criteria](https://doc.ibexa.co/en/latest/search/criteria_reference/search_criteria_reference/index.md) and [Sort Clause reference](https://doc.ibexa.co/en/latest/search/sort_clause_reference/sort_clause_reference/index.md) for details. > **Tip: Tip** > > It's recommended to use an IDE that can recognize type hints when working with Repository Filtering. If you try to use an unsupported Criterion or Sort Clause, the IDE indicates an issue. ## Search in controller You can use the `SearchService` or repository filtering in a controller, as long as you provide the required parameters. For example, in the code below, `locationId` is provided to list all children of a location by using the `SearchService`. ``` // ... use Ibexa\Bundle\Core\Controller; use Ibexa\Contracts\Core\Repository\SearchService; use Ibexa\Contracts\Core\Repository\Values\Content\LocationQuery; use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion; use Symfony\Component\HttpFoundation\Response; class CustomController extends Controller { // ... public function showContentAction(int $locationId): Response { $query = new LocationQuery(); $query->filter = new Criterion\ParentLocationId($locationId); $results = $this->searchService->findContentInfo($query); $items = []; foreach ($results->searchHits as $searchHit) { $items[] = $searchHit; } return $this->render('@ibexadesign/full/custom.html.twig', [ 'items' => $items, ]); } } ``` The rendering of results is then relegated to [templates](https://doc.ibexa.co/en/latest/templating/templates/templates/index.md) (lines 22-24). When using Repository filtering, provide the results of `ContentService::find()` as parameters to the view: ``` // ... use Ibexa\Bundle\Core\Controller; use Ibexa\Contracts\Core\Repository\ContentService; use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion\ParentLocationId; use Ibexa\Contracts\Core\Repository\Values\Filter\Filter; use Ibexa\Core\MVC\Symfony\View\ContentView; class CustomFilterController extends Controller { // ... public function showChildrenAction(ContentView $view): ContentView { $filter = new Filter(); $filter ->withCriterion(new ParentLocationId($view->getLocation()->id)); $view->setParameters( [ 'items' => $this->contentService->find($filter), ] ); return $view; } } ``` ### Paginate search results To paginate search or filtering results, it's recommended to use the [Pagerfanta library](https://github.com/BabDev/Pagerfanta) and [Ibexa DXP's adapters for it.](https://github.com/ibexa/core/blob/main/src/lib/Pagination/Pagerfanta/Pagerfanta.php) ``` // ... use Ibexa\Core\Pagination\Pagerfanta\ContentSearchAdapter; use Pagerfanta\Pagerfanta; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; class PaginationController extends Controller { // ... public function showContentAction(Request $request, int $locationId): Response { $query = new LocationQuery(); $query->filter = new Criterion\ParentLocationId($locationId); $pager = new Pagerfanta( new ContentSearchAdapter($query, $this->searchService) ); $pager->setMaxPerPage(3); $pager->setCurrentPage($request->get('page', 1)); return $this->render( '@ibexadesign/full/custom_pagination.html.twig', [ 'totalItemCount' => $pager->getNbResults(), 'pagerItems' => $pager, ] ); } } ``` Pagination can then be rendered for example using the following template: ``` {% for item in pagerItems %}

    {{ ibexa_content_name(item) }}

    {% endfor %} {% if pagerItems.haveToPaginate() %} {{ pagerfanta(pagerItems, 'ibexa') }} {% endif %} ``` For more information and examples, see [PagerFanta documentation](https://www.babdev.com/open-source/packages/pagerfanta/docs/2.x/usage). #### Pagerfanta adapters | Adapter class name | Description | | -------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [`ContentSearchAdapter`](https://github.com/ibexa/core/blob/main/src/lib/Pagination/Pagerfanta/ContentSearchAdapter.php) | Makes a search against passed Query and returns [`Content`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Content.html) objects. | | [`ContentSearchHitAdapter`](https://github.com/ibexa/core/blob/main/src/lib/Pagination/Pagerfanta/ContentSearchHitAdapter.php) | Makes a search against passed Query and returns [`SearchHit`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Search-SearchHit.html) objects instead. | | [`LocationSearchAdapter`](https://github.com/ibexa/core/blob/main/src/lib/Pagination/Pagerfanta/LocationSearchAdapter.php) | Makes a location search against passed Query and returns [`Location`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Location.html) objects. | | [`LocationSearchHitAdapter`](https://github.com/ibexa/core/blob/main/src/lib/Pagination/Pagerfanta/LocationSearchHitAdapter.php) | Makes a location search against passed Query and returns [`SearchHit`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Search-SearchHit.html) objects instead. | | [`ContentFilteringAdapter`](https://github.com/ibexa/core/blob/main/src/lib/Pagination/Pagerfanta/ContentFilteringAdapter.php) | Applies a Content filter and returns a [`ContentList`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-ContentList.html) object. | | [`LocationFilteringAdapter`](https://github.com/ibexa/core/blob/main/src/lib/Pagination/Pagerfanta/LocationFilteringAdapter.php) | Applies a location filter and returns a [`LocationList`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-LocationList.html) object. | | `AttributeDefinitionListAdapter` | Makes a search for product attributes and returns an [`AttributeDefinitionListInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-AttributeDefinition-AttributeDefinitionListInterface.html) object. | | `AttributeGroupListAdapter` | Makes a search for product attribute groups and returns an [`AttributeGroupListInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-AttributeGroup-AttributeGroupListInterface.html) object. | | `CurrencyListAdapter` | Makes a search for currencies and returns a [`CurrencyListInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Currency-CurrencyListInterface.html) object. | | `CustomPricesAdapter` | Makes a search for custom prices and returns a `CustomPrice` object. | | `CustomerGroupListAdapter` | Makes a search for customer groups and returns a [`CustomerGroupListInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-CustomerGroup-CustomerGroupListInterface.html) object. | | `ProductListAdapter` | Makes a search for products and returns a [`ProductListInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-ProductListInterface.html) object. | | `ProductTypeListAdapter` | Makes a search for product types and returns a [`ProductTypeListInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-ProductType-ProductTypeListInterface.html) object. | | `RegionListAdapter` | Makes a search for regions and returns a [`RegionListInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Region-RegionListInterface.html) object. | | `ShoppingListAdapter` | Makes a search for shopping lists and returns a [`ShoppingListCollectionInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-Value-ShoppingListCollectionInterface.html) object. | ## Complex search For more complex searches, you need to combine multiple Criteria. You can do it using logical operators: `LogicalAnd`, `LogicalOr`, and `LogicalNot`. ``` $query = new LocationQuery(); $query->query = new Criterion\LogicalAnd([ new Criterion\Subtree($this->locationService->loadLocation($locationId)->pathString), new Criterion\ContentTypeIdentifier($contentTypeIdentifier), ]); $result = $this->searchService->findContentInfo($query); $output->writeln('Found ' . $result->totalCount . ' items'); foreach ($result->searchHits as $searchHit) { $output->writeln($searchHit->valueObject->name); } ``` This example takes three parameters from a command — `$text`, `$contentTypeId`, and `$locationId`. It then combines them using `Criterion\LogicalAnd` to search for content items that belong to a specific subtree, have the chosen content type and contain the provided text (lines 3-6). This also shows that you can get the total number of search results using the `totalCount` property of search results (line 9). You can also nest different operators to construct more complex queries. The example below uses the `LogicalNot` operator to search for all content containing a given phrase that doesn't belong to the provided Section: ``` $query->query = new Criterion\LogicalAnd([ new Criterion\Subtree($this->locationService->loadLocation($locationId)->pathString), new Criterion\ContentTypeIdentifier($contentTypeIdentifier), new Criterion\FullText($text), new Criterion\LogicalNot( new Criterion\SectionIdentifier('Media') ), ]); ``` ### Combine independent Criteria Criteria are independent of one another. This can lead to unexpected behavior, for instance because content can have multiple locations. For example, a content item has two locations: visible location A and hidden location B. You perform the following query: ``` $query->filter = new Criterion\LogicalAnd([ new LocationId($bLocationId), new Visibility(Visibility::VISIBLE), ]); ``` The query searches for location B by using the [`LocationId` Criterion](https://doc.ibexa.co/en/latest/search/criteria_reference/locationid_criterion/index.md), and for visible content by using the [`Visibility` Criterion](https://doc.ibexa.co/en/latest/search/criteria_reference/visibility_criterion/index.md). Even though the location B is hidden, the query finds the content because both conditions are satisfied: - the content item has location B - the content item is visible (it has the visible location A) ## Sort results To sort the results of a query, use one of more [Sort Clauses](https://doc.ibexa.co/en/latest/search/sort_clause_reference/sort_clause_reference/index.md). For example, to order search results by their publication date, from oldest to newest, and then alphabetically by content name, add the following Sort Clauses to the query: ``` $query->sortClauses = [ new SortClause\DatePublished(LocationQuery::SORT_ASC), new SortClause\ContentName(LocationQuery::SORT_DESC), ]; ``` > **Tip: Tip** > > For the full list and details of available Sort Clauses, see [Sort Clause reference](https://doc.ibexa.co/en/latest/search/sort_clause_reference/sort_clause_reference/index.md). ## Aggregation > **Caution: Feature support** > > Aggregation is only available in the Solr and Elasticsearch search engines. With aggregations you can find the count of search results or other result information for each Aggregation type. To do this, you use of the query's `$aggregations` property: ``` $contentTypeTermAggregation = new ContentTypeTermAggregation('content_type'); $contentTypeTermAggregation->setLimit(5); $contentTypeTermAggregation->setMinCount(10); $query->aggregations[] = $contentTypeTermAggregation; ``` The name of the aggregation must be unique in the given query. Access the results by using the `get()` method of the aggregation: ``` $contentByType = $results->aggregations->get('content_type'); ``` Aggregation results contain the name of the result and the count of found items: ``` foreach ($contentByType as $contentType => $count) { $output->writeln($contentType->getName() . ': ' . $count); } ``` With field aggregations you can group search results according to the value of a specific field. In this case the aggregation takes the content type identifier and the field identifier as parameters. The following example creates an aggregation named `selection` that groups results according to the value of the `topic` field in the `article` content type: ``` $query->aggregations[] = new SelectionTermAggregation('selection', 'blog_post', 'topic'); ``` With term aggregation you can define additional limits to the results. The following example limits the number of terms returned to 5 and only considers terms that have 10 or more results: ``` $contentTypeTermAggregation = new ContentTypeTermAggregation('content_type'); $contentTypeTermAggregation->setLimit(5); $contentTypeTermAggregation->setMinCount(10); ``` To use a range aggregation, you must provide a `ranges` array containing a set of `Range` objects that define the borders of the specific range sets. ``` $query->aggregations[] = new IntegerRangeAggregation('range', 'person', 'age', [ new Query\Aggregation\Range(1,30), new Query\Aggregation\Range(30,60), new Query\Aggregation\Range(60,null), ]); ``` > **Note: Note** > > The beginning of the range is included and the end is excluded, so a range between 1 and 30 includes value `1`, but not `30`. > > `null` means that a range doesn't have an end. In the example all values above (and including) 60 are included in the last range. See [Aggregation reference](https://doc.ibexa.co/en/latest/search/aggregation_reference/aggregation_reference/index.md) for details of all available aggregations. ## Search with embeddings > **Note: Feature support** > > Searching with embeddings requires a search engine that supports it, such as Elasticsearch or Solr 9.8.1+. Embeddings are numerical representations that capture the meaning of text, images, or other content. AI providers generate embeddings by converting words or documents into lists of numbers, instead of treating them as plain text. Such lists, aka vectors, can then be compared to find content with similar meaning. Searching with embeddings enables matching content based on meaning rather than exact text matches. Instead of comparing keywords, the system compares vectors that represent the semantic meaning of content and the query input. > **Note: Taxonomy suggestions** > > Embedding queries have been introduced primarily to support the [Taxonomy suggestions](https://doc.ibexa.co/en/latest/content_management/taxonomy/taxonomy/#taxonomy-suggestions) feature, therefore embedding search integration is provided for `TaxonomyEmbedding`. You can narrow down the search results, for example, by content type or location. To do this, combine searching with embeddings with filters. Repository search also respects the permissions of the current user. An embedding query is represented by the [`Ibexa\Contracts\Core\Repository\Values\Content\EmbeddingQuery`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-EmbeddingQuery.html) value object. The object encapsulates the embedding used for similarity search and optional search parameters such as filtering, pagination, aggregations, and result counting. ### Use embedding queries in search Embedding queries are executed through the search API in the same way as other search requests. You build an `EmbeddingQuery` instance by using a builder and pass it to the search service. This example shows a minimal embedding query executed directly through the search service: ``` embeddingProviderResolver->resolve(); $embedding = $embeddingProvider->getEmbedding('example_content'); $query = EmbeddingQueryBuilder::create() ->withEmbedding(new TaxonomyEmbedding($embedding)) ->setFilter(new ContentTypeIdentifier('article')) ->setLimit(10) ->setOffset(0) ->setPerformCount(true) ->build(); $result = $this->searchService->findContent($query); $io->success(sprintf('Found %d items.', $result->totalCount)); foreach ($result->searchHits as $searchHit) { assert($searchHit instanceof SearchHit); /** @var \Ibexa\Contracts\Core\Repository\Values\Content\Content $content */ $content = $searchHit->valueObject; $contentInfo = $content->versionInfo->contentInfo; $io->writeln(sprintf( '%d: %s', $contentInfo->id, $contentInfo->name )); } return self::SUCCESS; } } ``` For more information, see [Embeddings reference](https://doc.ibexa.co/en/latest/search/embeddings_reference/embeddings_reference/index.md). ## Search in trash In the user interface, on the **Trash** screen, you can search for content items, and then sort the results based on different criteria. To search the trash with the API, use the `TrashService::findInTrash` method to submit a query for content items that are held in trash. Searching in trash supports a limited set of Criteria and Sort Clauses. For a list of supported Criteria and Sort Clauses, see [Search in trash reference](https://doc.ibexa.co/en/latest/search/search_in_trash_reference/index.md). > **Note: Note** > > Searching through the trashed content items operates directly on the database, therefore you cannot use external search engines, such as Solr or Elasticsearch, and it's impossible to reindex the data. ``` use Ibexa\Contracts\Core\Repository\TrashService; use Ibexa\Contracts\Core\Repository\Values\Content\Query; //... $query = new Query(); $query->filter = new Query\Criterion\ContentTypeId($contentTypeId); $results = $this->trashService->findTrashItems($query); foreach ($results->items as $trashedLocation) { $output->writeln($trashedLocation->getContentInfo()->name); } ``` > **Caution: Caution** > > Make sure that you set the Criterion on the `filter` property. It's impossible to use the `query` property, because the search in trash operation filters the database instead of querying. # Search Criteria and Sort Clauses Search Criteria and Sort Clauses are value object classes used for building a search query, to define filter criteria and ordering of the result set. Ibexa DXP provides a number of standard Search Criteria and Sort Clauses that you can use out of the box and that should cover the majority of use cases. For an example of how to use and combine Criteria and Sort Clauses, refer to [Searching in PHP API](https://doc.ibexa.co/en/latest/search/search_api/index.md). ## Search engine handling of Search Criteria and Sort Clauses As Search Criteria and Sort Clauses are value objects which are used to define the query from API perspective, they're common for all storage engines. Each storage engine needs to implement its own handlers for the corresponding Criterion and Sort Clause value object, which are used to translate the value object into a storage-specific search query. As an example take a look at the [`ContentId` Criterion handler](https://github.com/ibexa/core/blob/main/src/lib/Search/Legacy/Content/Common/Gateway/CriterionHandler/ContentId.php) in Legacy search engine or [`ContentId` Criterion handler](https://github.com/ibexa/solr/blob/main/src/lib/Query/Common/CriterionVisitor/ContentIdIn.php) in Solr search engine. ## Custom Criteria and Sort Clauses Sometimes you may find that standard Search Criteria and Sort Clauses provided with Ibexa DXP aren't sufficient for your needs. Most often this is the case if you have a custom field type using external storage which cannot be searched using the standard field Criterion. > **Note: Note** > > Legacy (SQL-based) search can also be used in `ibexa_keyword` external storage. In such cases you can implement a custom Criterion or Sort Clause, together with the corresponding handlers for the storage engine you're using. > **Caution: Using Field Criterion or Sort Clause with large databases** > > Field Criterion and Sort Clause don't perform well by design when using SQL database. If you have a large database and want to use them, you either need to use the Solr search engine, or develop your own Custom Criterion or Sort Clause. This way you can avoid using the attributes (fields) database table, and instead use a custom simplified table which can handle the amount of data you have. ### Difference between Content and Location Search There are two basic types of searches, you can either search for locations or for content. Each type has dedicated methods in the Search Service: | Type of search | Method in Search Service | | -------------- | ------------------------ | | Content | `findContent()` | | Content | `findContentInfo()` | | Content | `findSingle()` | | Location | `findLocations()` | All Criteria and Sort Clauses are accepted with Location Search, but not all of them can be used with Content Search. The reason for this is that while one location always has exactly one content item, one content item can have multiple locations. In this context some Criteria and Sort Clauses would produce ambiguous queries that would not be accepted by Content Search. Content Search explicitly refuses to accept Criteria and Sort Clauses implementing these abstract classes: - `Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion\Location` - `Ibexa\Contracts\Core\Repository\Values\Content\SortClause\Criterion\Location` ### Configuring custom Criterion and Sort Clause handlers After you have implemented your Criterion / Sort Clause and its handler, you need to configure the handler for the [service container](https://doc.ibexa.co/en/latest/api/php_api/php_api/#service-container) by using dedicated service tags for each type of search. Doing so automatically registers it and handle your Criterion / Search Clause when it's given as a parameter to one of the Search Service methods. Available tags for Criterion handlers in Legacy Storage Engine are: - `ibexa.search.legacy.gateway.criterion_handler.content` - `ibexa.search.legacy.gateway.criterion_handler.location` Available tags for Sort Clause handlers in Legacy Storage Engine are: - `ibexa.search.legacy.gateway.sort_clause_handler.content` - `ibexa.search.legacy.gateway.sort_clause_handler.location` > **Note: Note** > > You can find all the native handlers and the tags for the Legacy Storage Engine in files located in `core/src/lib/Resources/settings/storage_engines/`. > **Tip: Tip** > > When you search in trash, use the following service tags: > > - for Criterion handlers: `ibexa.core.trash.search.legacy.gateway.criterion_handler` > - for Sort Clause handlers: `ibexa.core.trash.search.legacy.gateway.sort_clause_handler` > > For more information about searching for content items in Trash, see [Search in trash](https://doc.ibexa.co/en/latest/search/search_api/#search-in-trash). > > For more information about the Criteria and Sort Clauses that are supported when searching for trashed content items, see [Searching in trash reference](https://doc.ibexa.co/en/latest/search/search_in_trash_reference/index.md). The following example shows how to register a ContentId Criterion handler, common for both Content and Location Search: ``` services: Ibexa\Core\Search\Legacy\Content\Common\Gateway\CriterionHandler\ContentId: arguments: ['@ibexa.api.storage_engine.legacy.dbhandler'] tags: - {name: ibexa.search.legacy.gateway.criterion_handler.content} - {name: ibexa.search.legacy.gateway.criterion_handler.location} ``` The following example shows how to register a Depth Sort Clause handler for Location Search: ``` Ibexa\Core\Search\Legacy\Content\Location\Gateway\SortClauseHandler\Location\Depth: arguments: ['@ibexa.api.storage_engine.legacy.dbhandler'] tags: - {name: ibexa.search.legacy.gateway.sort_clause_handler.location} ``` For more information about passing parameters, see [Symfony Service Container documentation](https://symfony.com/doc/7.4/service_container.html#service-parameters). ## Search using custom Field Criterion [REST] REST search can be performed by calling the `POST /views` method with a custom `FieldCriterion`. This allows you to build custom content logic queries with nested logical operators OR/AND/NOT. Custom Field Criterion search mirrors the one already existing in PHP API `Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion\Field` by exposing it to REST. #### Example of custom Content Query: ``` "ContentQuery":{ "Query":{ "OR":[ { "AND":[ { "Field":{ "name":"name", "operator":"CONTAINS", "value":"foo" } }, { "Field":{ "name":"info", "operator":"CONTAINS", "value":"bar" } } ] }, { "AND":[ { "Field":{ "name":"name", "operator":"CONTAINS", "value":"barfoo" } }, { "Field":{ "name":"info", "operator":"CONTAINS", "value":"baz" } } ] } ] } } ``` # Search Criteria reference Search Criteria are filters for content and location Search and [Repository filtering](https://doc.ibexa.co/en/latest/search/search_api/#repository-filtering). Criteria can take some of the following arguments: - `target` - when the Criterion supports targeting a specific field, example: `FieldDefinition` or Metadata identifier - `value` - the value(s) to filter on, typically a scalar or array of scalars - `operator` - constants on `Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion\Operator`: `IN`, `EQ`, `GT`, `GTE`, `LT`, `LTE`, `LIKE`, `BETWEEN`, `CONTAINS`. Most Criteria don't expose this and select `EQ` or `IN` depending on whether the value is scalar or an array. `IN` and `BETWEEN` always act on an array of values, while the other operators act on single scalar value - `valueData` - additional value data, required by some Criteria, for instance `MapLocationDistance` Support and capabilities of individual Criteria can depend on the search engine. In the Legacy search engine, the field index/sort key column is limited to 255 characters by design. Due to this storage limitation, searching content using the Country field type or Keyword when there are multiple values selected may not return all the expected results. ## Search Criteria | Search Criterion | Search based on | Content Search | Location Search | Filtering | Trash | | ---------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | -------------- | --------------- | --------- | ----- | | [Ancestor](https://doc.ibexa.co/en/latest/search/criteria_reference/ancestor_criterion/index.md) | Whether the content item is an ancestor of the provided location | Yes | Yes | Yes | | | [ContentId](https://doc.ibexa.co/en/latest/search/criteria_reference/contentid_criterion/index.md) | Content item's ID | Yes | Yes | Yes | | | [ContentName](https://doc.ibexa.co/en/latest/search/criteria_reference/contentname_criterion/index.md) | Content item's name | Yes | Yes | Yes | Yes | | [ContentTypeGroupId](https://doc.ibexa.co/en/latest/search/criteria_reference/contenttypegroupid_criterion/index.md) | ID of the content item's content type group | Yes | Yes | Yes | | | [ContentTypeId](https://doc.ibexa.co/en/latest/search/criteria_reference/contenttypeid_criterion/index.md) | ID of the content item's content type | Yes | Yes | Yes | Yes | | [ContentTypeIdentifier](https://doc.ibexa.co/en/latest/search/criteria_reference/contenttypeidentifier_criterion/index.md) | Identifier of the content item's content type | Yes | Yes | Yes | | | [CurrencyCodeCriterion](https://doc.ibexa.co/en/latest/search/criteria_reference/currencycode_criterion/index.md) | Currency code | Yes | Yes | Yes | | | [CustomField](https://doc.ibexa.co/en/latest/search/criteria_reference/customfield_criterion/index.md) | Custom field | Yes | Yes | | | | [DateMetadata](https://doc.ibexa.co/en/latest/search/criteria_reference/datemetadata_criterion/index.md) | The date when content was created or last modified | Yes | Yes | Yes | Yes | | [Depth](https://doc.ibexa.co/en/latest/search/criteria_reference/depth_criterion/index.md) | Location depth in the content tree | | Yes | Yes | | | [Field](https://doc.ibexa.co/en/latest/search/criteria_reference/field_criterion/index.md) | Content of one of content item's fields | Yes | Yes | | | | [FieldRelation](https://doc.ibexa.co/en/latest/search/criteria_reference/fieldrelation_criterion/index.md) | Content items the content in question has Relations to | Yes | Yes | | | | [FullText](https://doc.ibexa.co/en/latest/search/criteria_reference/fulltext_criterion/index.md) | Full text content of a content item's fields | Yes | Yes | | | | [Image](https://doc.ibexa.co/en/latest/search/criteria_reference/image_criterion/index.md) | Image by specified image attributes | Yes | Yes | | | | [ImageDimensions](https://doc.ibexa.co/en/latest/search/criteria_reference/imagedimensions_criterion/index.md) | Image dimensions: height and width | Yes | Yes | | | | [ImageFileSize](https://doc.ibexa.co/en/latest/search/criteria_reference/imagefilesize_criterion/index.md) | Image size in MB | Yes | Yes | | | | [ImageHeight](https://doc.ibexa.co/en/latest/search/criteria_reference/imageheight_criterion/index.md) | Image height in pixels | Yes | Yes | | | | [ImageMimeType](https://doc.ibexa.co/en/latest/search/criteria_reference/imagemimetype_criterion/index.md) | Image type | Yes | Yes | | | | [ImageOrientation](https://doc.ibexa.co/en/latest/search/criteria_reference/imageorientation_criterion/index.md) | Image orientation | Yes | Yes | | | | [ImageWidth](https://doc.ibexa.co/en/latest/search/criteria_reference/imagewidth_criterion/index.md) | Image width in pixels | Yes | Yes | | | | [IsBookmarked](https://doc.ibexa.co/en/latest/search/criteria_reference/isbookmarked_criterion/index.md) | Whether a location is bookmarked or not | | Yes | Yes | | | [IsContainer](https://doc.ibexa.co/en/latest/search/criteria_reference/iscontainer_criterion/index.md) | Whether a content item is a container (can contain other content items) | Yes | Yes | Yes | | | [IsCurrencyEnabledCriterion](https://doc.ibexa.co/en/latest/search/criteria_reference/iscurrencyenabled_criterion/index.md) | Whether a specified currency is enabled in the system | | | | | | [IsFieldEmpty](https://doc.ibexa.co/en/latest/search/criteria_reference/isfieldempty_criterion/index.md) | Whether a specified field of a content item is empty or not | Yes | Yes | | | | [IsMainLocation](https://doc.ibexa.co/en/latest/search/criteria_reference/ismainlocation_criterion/index.md) | Whether a location is the main location of a content item | | Yes | Yes | | | [IsProductBased](https://doc.ibexa.co/en/latest/search/criteria_reference/isproductbased_criterion/index.md) | Whether content represents a product | Yes | Yes | Yes | | | [IsUserBased](https://doc.ibexa.co/en/latest/search/criteria_reference/isuserbased_criterion/index.md) | Whether content represents a User account | Yes | Yes | Yes | | | [IsUserEnabled](https://doc.ibexa.co/en/latest/search/criteria_reference/isuserenabled_criterion/index.md) | Whether a User account is enabled | Yes | Yes | Yes | | | [LanguageCode](https://doc.ibexa.co/en/latest/search/criteria_reference/languagecode_criterion/index.md) | Whether a content item is translated into the selected language | Yes | Yes | Yes | | | [LocationId](https://doc.ibexa.co/en/latest/search/criteria_reference/locationid_criterion/index.md) | Location ID | Yes | Yes | Yes | | | [LocationRemoteId](https://doc.ibexa.co/en/latest/search/criteria_reference/locationremoteid_criterion/index.md) | Location remote ID | Yes | Yes | Yes | | | [MapLocationDistance](https://doc.ibexa.co/en/latest/search/criteria_reference/maplocationdistance_criterion/index.md) | Distance between the location contained in a MapLocation field and the provided coordinates | Yes | Yes | | | | [MatchAll](https://doc.ibexa.co/en/latest/search/criteria_reference/matchall_criterion/index.md) | Returns all search results | Yes | Yes | Yes | Yes | | [MatchNone](https://doc.ibexa.co/en/latest/search/criteria_reference/matchnone_criterion/index.md) | Returns no search results | Yes | Yes | Yes | Yes | | [ObjectStateId](https://doc.ibexa.co/en/latest/search/criteria_reference/objectstateid_criterion/index.md) | Object state ID | Yes | Yes | Yes | | | [ObjectStateIdentifier](https://doc.ibexa.co/en/latest/search/criteria_reference/objectstateidentifier_criterion/index.md) | Object state Identifier | Yes | Yes | Yes | | | [ParentLocationId](https://doc.ibexa.co/en/latest/search/criteria_reference/parentlocationid_criterion/index.md) | Location ID of a content item's parent | Yes | Yes | Yes | | | [ParentLocationRemoteId](https://doc.ibexa.co/en/latest/search/criteria_reference/parentlocationremoteId_criterion/index.md) | Location remote ID of a content item's parent | Yes | Yes | | | | [Priority](https://doc.ibexa.co/en/latest/search/criteria_reference/priority_criterion/index.md) | Location priority | | Yes | Yes | | | [RemoteId](https://doc.ibexa.co/en/latest/search/criteria_reference/remoteid_criterion/index.md) | Remote content ID | Yes | Yes | Yes | | | [SectionId](https://doc.ibexa.co/en/latest/search/criteria_reference/sectionid_criterion/index.md) | ID of the Section content is assigned to | Yes | Yes | Yes | Yes | | [SectionIdentifier](https://doc.ibexa.co/en/latest/search/criteria_reference/sectionidentifier_criterion/index.md) | Identifier of the Section content is assigned to | Yes | Yes | Yes | | | [Sibling](https://doc.ibexa.co/en/latest/search/criteria_reference/sibling_criterion/index.md) | Locations that are children of the same parent | Yes | Yes | Yes | | | [Subtree](https://doc.ibexa.co/en/latest/search/criteria_reference/subtree_criterion/index.md) | Location subtree | Yes | Yes | Yes | | | [TaxonomyEntryId](https://doc.ibexa.co/en/latest/search/criteria_reference/taxonomy_entry_id/index.md) | Content tagged with Entry ID | Yes | Yes | Yes | | | [TaxonomyNoEntries](https://doc.ibexa.co/en/latest/search/criteria_reference/taxonomy_no_entries/index.md) | Content with no entries assigned from a given taxonomy | Yes | Yes | Yes | | | [TaxonomySubtree](https://doc.ibexa.co/en/latest/search/criteria_reference/taxonomy_subtree/index.md) | Content assigned to a taxonomy entry or any of its descendants | Yes | Yes | | | | [UserEmail](https://doc.ibexa.co/en/latest/search/criteria_reference/useremail_criterion/index.md) | Email address of a User account | Yes | Yes | Yes | | | [UserId](https://doc.ibexa.co/en/latest/search/criteria_reference/userid_criterion/index.md) | User ID | Yes | Yes | Yes | | | [UserLogin](https://doc.ibexa.co/en/latest/search/criteria_reference/userlogin_criterion/index.md) | User login | Yes | Yes | Yes | | | [UserMetadata](https://doc.ibexa.co/en/latest/search/criteria_reference/usermetadata_criterion/index.md) | The creator or modifier of a content item | Yes | Yes | Yes | Yes | | [Visibility](https://doc.ibexa.co/en/latest/search/criteria_reference/visibility_criterion/index.md) | Whether the content item is visible or not | Yes | Yes | Yes | | ### Logical operators All Logical operators are supported by Content and Location Search and [Repository filtering](https://doc.ibexa.co/en/latest/search/search_api/#repository-filtering). | Search Criterion | Search based on | | ---------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | | [LogicalAnd](https://doc.ibexa.co/en/latest/search/criteria_reference/logicaland_criterion/index.md) | Implements a logical AND Criterion. It matches if ALL of the provided Criteria match. | | [LogicalNot](https://doc.ibexa.co/en/latest/search/criteria_reference/logicalnot_criterion/index.md) | Implements a logical NOT Criterion. It matches if the provided Criterion doesn't match. | | [LogicalOr](https://doc.ibexa.co/en/latest/search/criteria_reference/logicalor_criterion/index.md) | Implements a logical OR Criterion. It matches if at least one of the provided Criteria matches. | # Ancestor Criterion The [`Ancestor` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-Ancestor.html) searches for content that is an ancestor of the provided location, including this location. ## Arguments - `value` - array of location pathStrings ## Example ### PHP ``` $query->query = new Criterion\Ancestor([$this->locationService->loadLocation(62)->pathString]); ``` ### REST API **XML** ``` /81/82/ ``` **JSON** ``` "Query": { "Filter": { "AncestorCriterion": "/81/82/" } } ``` ## Use case You can use the Ancestor Search Criterion to create a list of breadcrumbs leading to the Location: ``` $query = new LocationQuery(); $query->query = new Criterion\Ancestor([$this->locationService->loadLocation($locationId)->pathString]); $results = $this->searchService->findLocations($query); $breadcrumbs = []; foreach ($results->searchHits as $searchHit) { $breadcrumbs[] = $searchHit; } return $this->render('parts/breadcrumbs.html.twig', [ 'breadcrumbs' => $breadcrumbs, ]); ``` ``` {% for breadcrumb in breadcrumbs %} {% if not loop.first %} -> {% endif %} {% if not loop.last %} {{ breadcrumb.valueObject.contentInfo.name }} {% else %} {{ breadcrumb.valueObject.contentInfo.name }} {% endif %} {% endfor %} ``` # ContentId Criterion The [`ContentId` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-ContentId.html) searches for content by its ID. ## Arguments - `value` - int(s) representing the Content ID(s) ## Example ### PHP ``` $query->query = new Criterion\ContentId([62, 64]); ``` ### REST API **XML** ``` 1,52 ``` **JSON** ``` "Query": { "Filter": { "ContentIdCriterion": "1,52" } } ``` # ContentName Criterion The [`ContentName` Search Criterion](https://github.com/ibexa/core/blob/main/src/contracts/Repository/Values/Content/Query/Criterion/ContentName.php) searches for content by its name. ## Arguments - `value` - string representing the content name, the wildcard character `*` can be used for partial search ## Example ### PHP ``` $query->query = new Criterion\ContentName('*phone'); ``` ### REST API **XML** ``` *phone ``` **JSON** ``` "Query": { "Filter": { "ContentNameCriterion": "*phone" } } ``` # ContentTypeGroupId Criterion The [`ContentTypeGroupId` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-ContentTypeGroupId.html) searches for content based on the ID of its content type group. ## Arguments - `value` - int(s) representing the content type group ID(s) ## Example ### PHP ``` $query->query = new Criterion\ContentTypeGroupId([1, 2]); ``` ### REST API **XML** ``` 1 ``` **JSON** ``` "Query": { "Filter": { "ContentTypeGroupIdCriterion": [1, 2] } } ``` ## Use case You can use the `ContentTypeGroupId` Criterion to query all Media content items (the default ID for the Media content type group is 3): ``` $query->query = new Criterion\ContentTypeGroupId([3]); $results = $this->searchService->findContent($query); $media = []; foreach ($results->searchHits as $searchHit) { $media[] = $searchHit; } } ``` # ContentTypeId Criterion The [`ContentTypeId` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-ContentTypeId.html) searches for content based on the ID of its content type. ## Arguments - `value` - int(s) representing the content type ID(s) ## Example ### PHP ``` $query->query = new Criterion\ContentTypeId([44]); ``` ### REST API **XML** ``` 44 ``` **JSON** ``` "Query": { "Filter": { "ContentTypeIdCriterion": 44 } } ``` # ContentTypeIdentifier Criterion The [`ContentTypeIdentifier` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-ContentTypeIdentifier.html) searches for content based on the identifier of its content type. ## Arguments - `value` - string(s) representing the content type identifier(s) ## Example ### PHP ``` $query->query = new Criterion\ContentTypeIdentifier(['article', 'blog_post']); ``` ### REST API **XML** ``` article ``` **JSON** ``` "Query": { "Filter": { "ContentTypeIdentifierCriterion": "article" } } ``` # CurrencyCode Criterion The `CurrencyCodeCriterion` Search Criterion searches for currencies by their codes. ## Arguments - `code` - string representing the currency code ## Limitations The `CurrencyCodeCriterion` Criterion isn't available in Solr or Elasticsearch engines. ## Example ### PHP ``` $query->query = new \Ibexa\Contracts\ProductCatalog\Values\Currency\Query\Criterion\CurrencyCodeCriterion('EUR'); ``` # Custom Field Criterion The [`CustomField` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-CustomField.html) searches for content or locations based on the contents of the search index fields. The allowed syntax and operator support might differ between search engines and the type of queried field. ## Arguments - `target` - string representing the identifier of the search index field - `operator` - one of [Operator](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-Operator.html) constants - `value` - the value to query for ## Limitations The `CustomField` Criterion isn't available in [Repository filtering](https://doc.ibexa.co/en/latest/search/search_api/#repository-filtering). ## Example ### PHP ``` query = new Query\Criterion\CustomField('content_name_s', Operator::EQ, '/Ibexa.*/'); /** @var \Ibexa\Contracts\Core\Repository\SearchService $searchService */ $searchService->findContent($query); ``` # CustomerGroupId Criterion The `CustomerGroupId` Search Criterion searches for content based on the ID of its customer group. ## Arguments - `value` - int(s) representing the customer group ID(s) ## Example ### PHP ``` $query->query = new Criterion\CustomerGroupId(1); ``` # DateMetadata Criterion The [`DateMetadata` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-DateMetadata.html) searches for content based on the date when it was created or last modified. ## Arguments - `target` - indicating if publication or modification date should be queried, either `DateMetadata::CREATED` or `DateMetadata::PUBLISHED` (both with the same functionality), or `DateMetadata::MODIFIED` - `operator` - Operator constant (IN, EQ, GT, GTE, LT, LTE, BETWEEN) - `value` - indicating the date(s) that should be matched, provided as a UNIX timestamp (or array of timestamps) ## Example ### PHP ``` $query->query = new Criterion\DateMetadata( Criterion\DateMetadata::CREATED, Criterion\Operator::BETWEEN, [1576800000, 1576972800] ); ``` ### REST API **XML** ``` modified 1675681020 gte ``` **JSON** ``` "Query": { "Filter": { "DateMetadataCriterion": { "Target": "modified", "Value": 1675681020, "Operator": "gte" } } } ``` ## Use case You can use the `DateMetadata` Criterion to search for blog posts that have been created within the last week: ``` $query = new LocationQuery; $date = strtotime("-1 week"); $query->query = new Criterion\LogicalAnd([ new Criterion\ContentTypeIdentifier('blog_post'), new Criterion\DateMetadata(Criterion\DateMetadata::CREATED, Criterion\Operator::GTE, $date), ] ); ``` # Depth Criterion The [`Location\Depth` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-Location-Depth.html) searches for locations based on their depth in the content tree. This Criterion is available only for Location Search. ## Arguments - `operator` - Operator constant (IN, EQ, GT, GTE, LT, LTE, BETWEEN) - `value` - int(s) representing the location depth(s) The `value` argument requires: - a list of ints for `Operator::IN` - exactly two ints for `Operator::BETWEEN` - a single int for other Operators ## Example ### PHP ``` $query->query = new Criterion\Location\Depth(Criterion\Operator::LT, 3); ``` # Field Criterion The [`Field` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-Field.html) searches for content based on the content of one of its fields. ## Arguments - `target` - string representing the identifier of the field to query - `operator` - operator constant (IN, EQ, GT, GTE, LT, LTE, LIKE, BETWEEN, CONTAINS) - `value` - the value to query for The `LIKE` operator works together with wildcards (`*`). Without a wildcards its results are the same as for the `EQ` operator. The `CONTAINS` operator works with collection fields like the Country field type, enabling you to retrieve results when the query value is one of the values of the collection. Querying for a collection with the `EQ` operator returns result only when the whole collection equals the query values. ## Limitations The `Field` Criterion isn't available in [Repository filtering](https://doc.ibexa.co/en/latest/search/search_api/#repository-filtering). ## Example ### PHP ``` $query->query = new Criterion\Field('name', Criterion\Operator::CONTAINS, 'Platform'); ``` ### REST API **XML** ``` name CONTAINS Platform ``` **JSON** ``` { "Query": { "Filter": { "Field": { "name": "name", "operator": "CONTAINS", "value": "Platform" } } } } ``` ## Use case You can use the `Field` Criterion to search for articles that contain the word "featured": ``` $query = new LocationQuery(); $query->query = new Criterion\LogicalAnd([ new Criterion\ContentTypeIdentifier('article'), new Criterion\Field('name', Criterion\Operator::CONTAINS, 'Featured') ] ); ``` # FieldRelation Criterion The [`FieldRelation` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-FieldRelation.html) searches for content based on the content items it has Relations to. ## Arguments - `target` - string representing the identifier of the Field containing Relations - `operator` - Operator constant (IN, EQ, GT, GTE, LT, LTE, BETWEEN) - `value` - array of ints representing the Relation content IDs to search for Use of IN means the Relation needs to have one of the provided IDs, while CONTAINS implies it needs to have all provided IDs. ## Limitations The `FieldRelation` Criterion isn't available in [Repository filtering](https://doc.ibexa.co/en/latest/search/search_api/#repository-filtering). ## Example ### PHP ``` $query->query = new Criterion\FieldRelation('relations', Criterion\Operator::CONTAINS, [55, 63]); ``` # Full-Text Criterion The [`FullText` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-FullText.html) searches for content based on the full text content of its fields. ## Arguments - `value` - string to search for ## Supported syntax | Feature | Elasticsearch | Apache Solr | Legacy Search Engine (SQL) | | --------------------------------- | ------------- | ----------- | -------------------------- | | Boolean operators: AND (&&), OR ( | | ), NOT (!) | No\* | | Require/exclude operators: +, - | No | Yes | No | | Grouping with parentheses | No | Yes | No | | Phrase search with double quotes | No | Yes | No | | Asterisks (\*) as wildcards | No | Yes | Yes, limited\*\*\* | \* When using the Elasticsearch search engine, a full text query performs an OR query by default, while the OR and AND operators return unexpected results. \*\* When using the Legacy search engine, a full text query performs an OR query. \*\*\* Asterisk may only be located at the beginning or end of a query. ## Limitations When using the Legacy search engine, a full text query performs an OR query by default, and supports asterisks as wildcards located at the beginning or end of a query. When using the Elasticsearch search engine, a full text query performs an OR query by default, while the OR and AND operators return unexpected results. The `FullText` Criterion isn't available in [Repository filtering](https://doc.ibexa.co/en/latest/search/search_api/#repository-filtering). ## Example ### PHP ``` $query->query = new Criterion\FullText('victory'); ``` Using double quotes to indicate a phrase: ``` $query->query = new Criterion\FullText('"world cup"'); ``` Using the AND operator and parenthesis to search for both words at the same time: ``` $query->query = new Criterion\FullText('baseball AND cup'); ``` ### REST API **XML** ``` victory ``` **JSON** ``` "Query": { "Filter": { "FullTextCriterion": "victory" } } ``` ## Use cases Assume the following search query: ``` $query->query = new Criterion\FullText('(cup AND ba*ball) "breaking news"'); ``` It returns content containing phrases such as "Breaking news", "Baseball world cup", "Basketball cup", or "Breaking news: Baseball world cup victory". It doesn't return content with phrases such as "Football world cup" or "Breaking sports news". # Image Criterion The `Image` Search Criterion searches for image by specified image attributes. ## Arguments - `fieldDefIdentifier` - string representing the identifier of the field - `imageCriteriaData` - array representing image attributes. All attributes are optional. ## Example ### PHP ``` $imageCriteriaData = [ 'mimeTypes' => [ 'image/png', ], 'orientation' => [ 'image/png', ], 'width' => [ 'min' => 0, // (default: 0, optional) 'max' => 1000, // (default: null, optional) ], 'height' => [ 'min' => 0, // (default: 0, optional) 'max' => 1000, // (default: null, optional) ], 'size' => [ 'min' => 0, // (default: 0, optional) 'max' => 2, // (default: null, optional) ], ]; $query->query = new Criterion\Image('image', $imageCriteriaData); ``` ### REST API **XML** ``` image image/png 0 2 100 1000 500 1500 portrait ``` **JSON** ``` "Query": { "Filter": { "ImageCriterion": { "fieldDefIdentifier": "image", "mimeTypes": "image/png", "size": { "max": 1.5 }, "width": { "max": 1000 }, "height": { "max": 1500 }, "orientation": "portrait" } } } OR "Query": { "Filter": { "ImageCriterion": { "fieldDefIdentifier": "image", "mimeTypes": [ "image/png", "image/jpeg" ], "size": { "min": 0, "max": 2 }, "width": { "min": 100, "max": 1000 }, "height": { "min": 500, "max": 1500 }, "orientation": [ "portrait", "landscape" ] } } } ``` # Image Dimension Criterion The `Dimensions` Search Criterion searches for image with specified dimensions. ## Arguments - `fieldDefIdentifier` - string representing the identifier of the field - `imageCriteriaData` - an array representing minimum and maximum values for width and height, expressed in pixels ## Example ### PHP ``` $imageCriteriaData = [ 'width' => [ 'min' => 100, // (default: 0, optional) 'max' => 1000, // (default: null, optional) ], 'height' => [ 'min' => 500, // (default: 0, optional) 'max' => 1500, // (default: null, optional) ], ]; $query->query = new Criterion\Dimensions('image', $imageCriteriaData); ``` ### REST API **XML** ``` image 100 1000 500 1500 ``` **JSON** ``` "Query": { "Filter": { "ImageDimensionsCriterion": { "fieldDefIdentifier": "image", "width": { "min": 100, "max": 1000 }, "height": { "min": 500, "max": 1500 } } } } ``` # Image FileSize Criterion The `FileSize` Search Criterion searches for image with specified size. ## Arguments - `fieldDefIdentifier` - string representing the identifier of the field - (optional) `minValue` - numeric representing minimum file size expressed in MB, default: 0 - (optional) `maxValue` - numeric representing maximum file size expressed in MB, default: `null` ## Example ### PHP ``` $query->query = new Criterion\FileSize('image', 0, 1.5); ``` ### REST API **XML** ``` image 0 1.5 ``` **JSON** ``` "Query": { "Filter": { "ImageFileSizeCriterion":{ "fieldDefIdentifier": "image", "size": { "min": 0, "max": 1.5 } } } } ``` # Image Height Criterion The `Height` Search Criterion searches for image with specified height. ## Arguments - `fieldDefIdentifier` - string representing the identifier of the field - (optional) `minValue` - int representing minimum file height expressed in pixels, default: 0 - (optional) `maxValue` - int representing maximum file height expressed in pixels, default: `null` ## Example ### PHP ``` $query->query = new Criterion\Height('image', 0, 1500); ``` # Image MimeType Criterion The `MimeType` Search Criterion searches for image with specified mime type(s). ## Arguments - `fielDefIdentifier` - string representing the identifier of the field - `type` - string(s) representing mime type(s) ## Example ### PHP ``` $query->query = new Criterion\MimeType('image', 'image/jpeg'); ``` or ``` $mimeTypes = [ 'image/jpeg', 'image/png', ]; $query->query = new Criterion\MimeType('image', $mimeTypes); ``` ### REST API **XML** ``` image image/png ``` **JSON** ``` "Query": { "Filter": { "ImageMimeTypeCriterion": { "fieldDefIdentifier": "image", "type": "image/png" } } } OR "Query": { "Filter": { "ImageMimeTypeCriterion": { "fieldDefIdentifier": "image", "type": ["image/png", "image/jpeg"] } } } ``` # Image Orientation Criterion The `Orientation` Search Criterion searches for image with specified orientation(s). Supported orientation values: landscape, portrait and square. ## Arguments - `fielDefIdentifier` - string representing the identifier of the field - `orientation` - strings representing orientations ## Example ### PHP ``` $query->query = new Criterion\Orientation('image', 'landscape'); OR $orientations = [ 'landscape', 'portrait', ]; $query->query = new Criterion\Orientation('image', $orientations); ``` ### REST API **XML** ``` image landscape ``` **JSON** ``` "Query": { "Filter": { "ImageOrientationCriterion": { "fieldDefIdentifier": "image", "orientation": "landscape" } } } OR "Query": { "Filter": { "ImageOrientationCriterion": { "fieldDefIdentifier": "image", "orientation": ["portrait", "landscape"] } } } ``` # Image Width Criterion The `Width` Search Criterion searches for image with specified width. ## Arguments - `fieldDefIdentifier` - string representing the identifier of the field - (optional) `minValue` - int representing minimum file width expressed in pixels, default: 0 - (optional) `maxValue` - int representing maximum file width expressed in pixels, default: `null` ## Example ### PHP ``` $query->query = new Criterion\Width('image', 150, 1000); ``` # IsBookmarked Criterion The [`IsBookmarked` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-Location-IsBookmarked.html) searches for location based on whether it's bookmarked or not. It works with current user reference. This Criterion is available only for location Search. ## Arguments - `value` - bool representing whether to search for bookmarked location (default `true`) or not bookmarked location (`false`) ## Example ### PHP ``` use Ibexa\Contracts\Core\Repository\Values\Content\LocationQuery; use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion\Location\IsBookmarked; $query = new LocationQuery(); $query->filter = new IsBookmarked(); /** @var \Ibexa\Contracts\Core\Repository\SearchService $searchService */ $results = $searchService->findLocations($query); ``` ### REST API **XML** ``` true ``` **JSON** ``` "Query": { "Filter": { "IsBookmarkedCriterion": true } } ``` # IsContainer Criterion The [`IsContainer` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-IsContainer.html) searches for content items based on whether they are containers (i.e., can contain other content items). ## Arguments - `value` – boolean (optional, default: `true`). If `true`, searches for content that is a container. If `false`, searches for content that is not a container. ## Example ### PHP ``` $query->query = new Criterion\IsContainer(); // Finds containers $query->query = new Criterion\IsContainer(false); // Finds non-containers ``` # IsCurrencyEnabledCriterion Criterion The `IsCurrencyEnabledCriterion` Search Criterion searches for currencies that are enabled in the system. ## Arguments - (optional) `enabled` - bool representing whether to search for enabled (default `true`), or disabled Currencies (`false`) ## Limitations The `IsCurrencyEnabledCriterion` Criterion isn't available in Solr or Elasticsearch engines. ## Example ### PHP ``` $query->query = new \Ibexa\Contracts\ProductCatalog\Values\Currency\Query\Criterion\IsCurrencyEnabledCriterion(); ``` # IsFieldEmpty Criterion The [`IsFieldEmpty` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-IsFieldEmpty.html) searches for content based on whether a specified field is empty or not. ## Arguments - `fieldDefinitionIdentifier` - string representing the identifier of the field - (optional) `value` - bool representing whether to search for empty (default `true`), or non-empty fields (`false`) ## Limitations The `IsFieldEmpty` Criterion isn't available in [Repository filtering](https://doc.ibexa.co/en/latest/search/search_api/#repository-filtering). The Richtext field type (`ibexa_richtext`) isn't searchable in the Legacy search engine. The `IsFieldEmpty` criterion doesn't work for [Taxonomy entry assignment](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/taxonomyentryassignmentfield/index.md) fields. For this use case, use [`TaxonomyNoEntries`](https://doc.ibexa.co/en/latest/search/criteria_reference/taxonomy_no_entries/index.md) instead. ## Example ### PHP ``` $query->query = new Criterion\IsFieldEmpty('title'); ``` ## Use case You can use the `IsFieldEmpty` Criterion to search for articles that don't have an image: ``` $query = new LocationQuery; $query->query = new Criterion\LogicalAnd([ new Criterion\ContentTypeIdentifier('article'), new Criterion\IsFieldEmpty('image'), ] ); ``` # IsMainLocation Criterion The [`IsMainLocation` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-LanguageCode.html) searches for locations based on whether they're the main location of a content item or not. This Criterion is available only for Location Search. ## Arguments - `value` - `IsMainLocation::MAIN` (0) or `IsMainLocation::NOT_MAIN` (1), representing whether to search for a main or not main location ## Example ### PHP ``` $query->query = new Criterion\Location\IsMainLocation(IsMainLocation::MAIN); ``` # IsProductBased Criterion The `IsProductBased` Search Criterion searches for content that plays the role of a Product. ## Example ### PHP ``` $query->query = new Ibexa\Contracts\ProductCatalog\Values\Content\Query\Criterion\IsProductBased(); ``` # IsUserBased Criterion The [`IsUserBased` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-IsUserBased.html) searches for content that plays the role of a User account. > **Note: Note** > > In the default setup only the user content type is treated as user accounts. However, you can also [set other content types to be treated as such](https://doc.ibexa.co/en/latest/administration/configuration/repository_configuration/#user-identifiers). ## Arguments - (optional) `value` - bool representing whether to search for User-based (default `true`) or non-User-based content ## Limitations The `IsUserBased` Criterion isn't available in Solr or Elasticsearch engines. ## Example ### PHP ``` $query->query = new Criterion\IsUserBased(); ``` ### REST API **XML** ``` false ``` **JSON** ``` "Query": { "Filter": { "IsUserBasedCriterion": "false" } } ``` # IsUserEnabled Criterion The [`IsUserEnabled` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-IsUserEnabled.html) searches for user accounts that are enabled or disabled. ## Arguments - (optional) `value` - bool representing whether to search for enabled (default `true`) or disabled user accounts ## Example ### PHP ``` $query->query = new Criterion\IsUserEnabled(); ``` ### REST API **XML** ``` true ``` **JSON** ``` "Query": { "Filter": { "IsUserEnabledCriterion": "true" } } ``` # LanguageCode Criterion The [`LanguageCode` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-Location.html) searches for content based on whether it's translated into the selected language. ## Arguments - `value` - string(s) representing the language codes to search for - (optional) `matchAlwaysAvailable` - bool representing whether content with the `alwaysAvailable` flag should be returned even if it doesn't contain the selected language (default `true`) ## Example ### PHP ``` $query->query = new Criterion\LanguageCode('ger-DE', false); ``` ### REST API **XML** ``` eng-GB ``` **JSON** ``` "Query": { "Filter": { "LanguageCodeCriterion": "eng-GB" } } ``` ## Use case You can use the `LanguageCode` Criterion to search for articles that are lacking a translation into a specific language: ``` $query = new LocationQuery; $query->query = new Criterion\LogicalAnd([ new Criterion\ContentTypeIdentifier('article'), new Criterion\LogicalNot( new Criterion\LanguageCode('ger-DE', false) ) ] ); $results = $this->searchService->findContent($query); $articles = []; foreach ($results->searchHits as $searchHit) { $articles[] = $searchHit; } return $this->render('list/articles_to_translate.html.twig', [ 'articles' => $articles, ]); ``` # LocationId Criterion The [`LocationId` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-LocationId.html) searches for content based in the location ID. ## Arguments - `value` - int(s) representing the location ID(s) ## Example ### PHP ``` $query->query = new Criterion\LocationId(62); ``` ### REST API **XML** ``` 62 ``` **JSON** ``` "Query": { "Filter": { "LocationIdCriterion": "62" } } ``` # LocationRemoteId Criterion The [`LocationRemoteId` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-LocationRemoteId.html) searches for content based in the location remote ID. ## Arguments - `value` - string(s) representing the location remote ID(s) ## Example ### PHP ``` $query->query = new Criterion\LocationRemoteId(['4d1e5f216c0a7aaab7f005ffd4b6a8a8', 'b81ef3e62b514188bfddd2a80d447d34']); ``` ### REST API **XML** ``` 3aaeefdb0ae573ac91f6d6ea78d230b7 ``` **JSON** ``` "Query": { "Filter": { "LocationRemoteIdCriterion": "3aaeefdb0ae573ac91f6d6ea78d230b7" } } ``` # MapLocationDistance Criterion The [`MapLocationDistance` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-MapLocationDistance.html) searches content based on the distance between the location contained in a MapLocation field and the provided coordinates. ## Arguments - `target` - string representing the field definition identifier - `operator` - Operator constant (IN, EQ, GT, GTE, LT, LTE, BETWEEN) - `distance` - float(s) representing the distances between the map location in the field and the location provided in `latitude` and `longitude` arguments - `latitude` - float representing the latitude of the location to calculate distance to - `longitude` - float representing the longitude of the location to calculate distance to The `distance` argument requires: - a list of floats for `Operator::IN` or `Operator::BETWEEN` - a single float for other Operators ## Limitations The `MapLocationDistance` Criterion isn't available in [Repository filtering](https://doc.ibexa.co/en/latest/search/search_api/#repository-filtering). ## Example ### PHP ``` $query->query = new Criterion\MapLocationDistance('location', Criterion\Operator::LTE, 5, 51.395973, 22.531696); ``` # MatchAll Criterion The [`MatchAll` content](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-MatchAll.html) and [`MatchAll` product](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-Criterion-MatchAll.html) search criteria are auxiliary criteria that returns all search results. They're used internally when no filter or query is provided on a Query object. The criteria take no arguments. # MatchNone Criterion The [`MatchNone` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-MatchNone.html) is an auxiliary Criterion that returns no search results. It's used internally when no filter or query is provided on a Query object. The Criterion takes no arguments. # ObjectStateId Criterion The [`ObjectStateId` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-ObjectStateId.html) searches for content based on its object state ID. ## Arguments - `value` - int(s) representing the object state ID(s) ## Example ### PHP ``` $query->query = new Criterion\ObjectStateId([4, 5]); ``` ### REST API **XML** ``` 1 ``` **JSON** ``` "Query": { "Filter": { "ObjectStateIdCriterion": "1" } } ``` # ObjectStateIdentifier Criterion The [`ObjectStateIdentifier` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-ObjectStateId.html) searches for content based on its object state identifier. ## Arguments - `value` - string(s) representing the object state identifier(s) - `target` (optional for PHP) - string representing the object state group ## Example ### PHP ``` $query->query = new Criterion\ObjectStateIdentifier(['ready']); ``` ``` $query->query = new Criterion\ObjectStateIdentifier(['not_locked'], 'ibexa_lock'); ``` ### REST API **XML** ``` not_locked ibexa_lock ``` **JSON** ``` { "Query": { "Filter": { "ObjectStateIdentifierCriterion": { "value": "not_locked", "target": "ibexa_lock" } } } } ``` # ParentLocationId Criterion The [`ParentLocationId` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-ParentLocationId.html) searches for content based on the Location ID of its parent. ## Arguments - `value` - int(s) representing the parent location IDs ## Example ### PHP ``` $query->query = new Criterion\ParentLocationId([54, 58]); ``` ### REST API **XML** ``` [81, 82] ``` **JSON** ``` "Query": { "Filter": { "ParentLocationIdCriterion": [69, 72] } } ``` ## Use case You can use the `ParentLocationId` Search Criterion to list blog posts contained in a blog: ``` $query = new LocationQuery(); $query->query = new Criterion\LogicalAnd([ new Criterion\Visibility(Criterion\Visibility::VISIBLE), new Criterion\ParentLocationId($locationId), ]); $results = $this->searchService->findLocations($query); $posts = []; foreach ($results->searchHits as $searchHit) { $posts[] = $searchHit; } return $this->render('full/blog.html.twig', [ 'posts' => $posts, ]); ``` ```

    Posts:

      {% for post in posts %}
    • {{ post.valueObject.contentInfo.name }}
    • {% endfor %}
    ``` # ParentLocationRemoteId Criterion The `ParentLocationRemoteId` Search Criterion searches for content based on the location remote ID of its parent. ## Arguments - `value` - int(s) representing the parent location remote IDs ### REST API **XML** ``` abab615dcf26699a4291657152da4337 ``` **JSON** ``` "Query": { "Filter": { "ParentLocationRemoteIdCriterion": "abab615dcf26699a4291657152da4337" } } ``` # Priority Criterion The [`Location\Priority` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-Location-Priority.html) searches for locations based on their priority. This Criterion is available only for Location Search. ## Arguments - `operator`- Operator constant (GT, GTE, LT, LTE, BETWEEN) - `value` - int(s) representing the priority The `value` argument requires: - a list of ints for `Operator::BETWEEN` - a single int for other Operators ## Example ### PHP ``` $query->query = new Criterion\Location\Priority(Criterion\Operator::GTE, 50); ``` # RemoteId / ContentRemoteId Criterion The [`RemoteId` / `ContentRemoteId` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-RemoteId.html) searches for content based on its remote content ID. ## Arguments - `value` - string(s) representing the remote IDs ## Example ### PHP ``` $query->query = new Criterion\RemoteId('abab615dcf26699a4291657152da4337'); ``` ### REST API **XML** ``` abab615dcf26699a4291657152da4337 ``` **JSON** ``` "Query": { "Filter": { "ContentRemoteIdCriterion": "abab615dcf26699a4291657152da4337" } } ``` # SectionId Criterion The [`SectionId` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-SectionId.html) searches for content based on the ID of the Section it's assigned to. ## Arguments - `value` - int(s) representing the IDs of the Section(s) ## Example ### PHP ``` $query->query = new Criterion\SectionId(3); ``` ### REST API **XML** ``` 3 ``` **JSON** ``` "Query": { "Filter": { "SectionIdCriterion": "3" } } ``` # SectionIdentifier Criterion The [`SectionIdentifier` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-SectionIdentifier.html) searches for content based on the identifier of the Section it's assigned to. ## Arguments - `value` - string(s) representing the identifiers of the Section(s) ## Example ### PHP ``` $query->query = new Criterion\SectionIdentifier(['sports', 'news']); ``` ### REST API **XML** ``` sports ``` **JSON** ``` "Query": { "Filter": { "SectionIdentifierCriterion": "sports" } } ``` # Sibling Criterion The [`Sibling` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-Sibling.html) searches for content under the same parent as the indicated location. ## Arguments - `locationId` - int representing the location ID - `parentLocationId` - int representing the parent location ID ## Example ### PHP ``` $query->query = new Criterion\Sibling(59, 2); ``` You can also use the named constructor `Criterion\Sibling::fromLocation` and provide it with the location object: ``` $location = $locationService->loadLocation(59); $query->query = Criterion\Sibling::fromLocation($location); ``` ### REST API ### REST API **XML** ``` 85 81 ``` **JSON** ``` "Query": { "Filter": { "SiblingCriterion": { "locationId": 85, "parentLocationId": 81 } } } ``` # Subtree Criterion The [`Subtree` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-Subtree.html) searches for content based on its location ID subtree path. It returns the content item and all the content items below it in the subtree. ## Arguments - `value` - string(s) representing the pathstring(s) to search for ## Example ### PHP ``` $query->query = new Criterion\Subtree('/1/2/71/72/'); ``` ### REST API **XML** ``` /1/2/71/ ``` **JSON** ``` "Query": { "Filter": { "SubtreeCriterion": "/1/2/71/" } } ``` # TaxonomyEntryId Criterion The [`TaxonomyEntryId` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Taxonomy-Search-Query-Criterion-TaxonomyEntryId.html) searches for content based on the ID of the Taxonomy Entry it's assigned to. ## Arguments - `value` - int(s) representing the IDs of the Tag(s) ## Example ### PHP ``` $query->query = new Criterion\TaxonomyEntryId(1); ``` Add an array of ID's to find Content tagged with at least one of the tags (OR). ``` $query->query = new Criterion\TaxonomyEntryId([1, 2, 3]); ``` # TaxonomyNoEntries Criterion The [`TaxonomyNoEntries`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Taxonomy-Search-Query-Criterion-TaxonomyNoEntries.html) Search Criterion searches for content that has no entries assigned from the specified [taxonomy](https://doc.ibexa.co/en/latest/content_management/taxonomy/taxonomy/index.md). Use it when you need to find content items to which no taxonomy entries have been assigned (for example, articles without tags). It's available for all supported search engines and in [repository filtering](https://doc.ibexa.co/en/latest/search/search_api/#repository-filtering). ## Arguments - `taxonomy` - `string` representing the identifier of the taxonomy (for example, `tags` or `categories`) ## Example ### PHP The following example searches for articles that have no entries assigned in the `tags` taxonomy: ``` query = new LogicalAnd( [ new TaxonomyNoEntries('tags'), new ContentTypeIdentifier('article'), ] ); /** @var \Ibexa\Contracts\Core\Repository\SearchService $searchService */ $results = $searchService->findContent($query); ``` The criteria limit the results to content that matches all of the conditions listed below: - content has no entries assigned in the `tags` taxonomy - content type is `article` # TaxonomySubtree Criterion The [`TaxonomySubtree`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Taxonomy-Search-Query-Criterion-TaxonomySubtree.html) Search Criterion searches for content assigned to the specified [taxonomy](https://doc.ibexa.co/en/latest/content_management/taxonomy/taxonomy/index.md) entry or any of its descendants. ## Arguments - `taxonomyEntryId` - `int` representing the ID of the taxonomy entry that is the root of the subtree ## Example ### PHP The following example searches for articles assigned to taxonomy entry with ID `42` or any of its child entries: ``` query = new LogicalAnd( [ new TaxonomySubtree(42), new ContentTypeIdentifier('article'), ] ); /** @var \Ibexa\Contracts\Core\Repository\SearchService $searchService */ $results = $searchService->findContent($query); ``` The criteria limit the results to content that match all of the conditions listed below: - content is assigned to taxonomy entry `42` or any of its descendants - content type is `article` # UserEmail Criterion The [`UserEmail` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-UserEmail.html) searches for content based on the email assigned to the user account. ## Arguments - `value` - string(s) representing the User email(s) - (optional) `operator` - operator constant (IN, EQ, LIKE) ## Limitations Solr search engine and Elasticsearch support IN and EQ operators only. ## Example ### PHP ``` $query->query = new Criterion\UserEmail(['johndoe']); ``` ``` $query->query = new Criterion\UserEmail('nospam*', Criterion\Operator::LIKE); ``` ### REST API **XML** ``` j.black* ``` **JSON** ``` "Query": { "Filter": { "UserEmailCriterion": "j.black*" } } ``` # UserId Criterion The [`UserId` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-UserId.html) searches for content based on the User ID. ## Arguments - `value` - int(s) representing the User ID(s) ## Example ### PHP ``` $query->query = new Criterion\UserId([14]); ``` ### REST API **XML** ``` 14 ``` **JSON** ``` "Query": { "Filter": { "UserIdCriterion": "14" } } ``` # UserLogin Criterion The [`UserLogin` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-UserLogin.html) searches for content based on the User ID. ## Arguments - `value` - string(s) representing the User logins(s) - (optional) `operator` - operator constant (IN, EQ, LIKE) ## Limitations Solr search engine and Elasticsearch support IN and EQ operators only. ## Example ### PHP ``` $query->query = new Criterion\UserLogin(['johndoe']); ``` ``` $query->query = new Criterion\UserLogin('adm*', Criterion\Operator::LIKE); ``` ### REST API **XML** ``` johndoe ``` **JSON** ``` "Query": { "Filter": { "UserLoginCriterion": "johndoe" } } ``` # UserMetadata Criterion The [`UserMetadata` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-UserMetadata.html) searches for content based on its creator or modifier. ## Arguments - `target` - UserMetadata constant (OWNER, GROUP, MODIFIER); GROUP means the user group of the content item's creator - `operator` - Operator constant (EQ, IN) - `value` - int(s) representing the User IDs or user group IDs (in case of the UserMetadata::GROUP target) ## Example ### PHP ``` $query->query = new Criterion\UserMetadata(Criterion\UserMetadata::GROUP, Criterion\Operator::EQ, 12); ``` ### REST API **XML** ``` GROUP EQ 12 ``` **JSON** ``` { "Query": { "Filter": { "UserMetadataCriterion": { "target": "GROUP", "operator": "EQ", "value": 12 } } } } ``` ## Use case You can use the `UserMetadata` Criterion to search for blog posts created by the Contributor user group: ``` // ID of your custom Contributor User Group $contributorGroupId = 32; $query = new LocationQuery; $query->query = new Criterion\LogicalAnd([ new Criterion\ContentTypeIdentifier('blog_post'), new Criterion\UserMetadata(Criterion\UserMetadata::GROUP, Criterion\Operator::EQ, $contributorGroupId) ] ); ``` # Visibility Criterion The [`Visibility` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-Visibility.html) searches for content based on whether it's visible or not. This Criterion takes into account both hiding content and hiding locations. When used with Content Search, the Criterion takes into account all assigned locations. This means that hidden content is returned if it has at least one visible location. Use Location Search to avoid this. ## Arguments - `value` - Visibility constant (VISIBLE, HIDDEN) ## Example ### PHP ``` $query->query = new Criterion\Visibility(Criterion\Visibility::HIDDEN); ``` ### REST API **XML** ``` HIDDEN ``` **JSON** ``` "Query": { "Filter": { "VisibilityCriterion": "HIDDEN" } } ``` # LogicalAnd Criterion The [`LogicalAnd` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-LogicalAnd.html) matches content if all provided Criteria match. When querying for [products](https://doc.ibexa.co/en/latest/product_catalog/product_api/index.md), use [LogicalAnd](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-Criterion-LogicalAnd.html) instead. ## Arguments - `criterion` - a set of Criteria combined by the logical operator ## Example ### PHP ``` $query->query = new Criterion\LogicalAnd([ new Criterion\ContentTypeIdentifier('article'), new Criterion\SectionIdentifier(['sports', 'news']); ] ); ``` ### REST API **XML** ``` article news ``` **JSON** ``` { "Query": { "Filter": { "AND": { "ContentTypeIdentifierCriterion": "article", "SectionIdentifierCriterion": "news" } } } } ``` # LogicalNot Criterion The [`LogicalNot` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-LogicalNot.html) matches content URL if the provided Criterion doesn't match. It takes only one Criterion in the array parameter. ## Arguments - `criterion` - represents the Criterion that should be negated ## Example ``` $query->filter = new Criterion\LogicalNot( new Criterion\ContentTypeIdentifier($contentTypeId) ); ``` ### REST API **XML** ``` article ``` **JSON** ``` { "Query": { "Criterion": { "LogicalNotCriterion": { "ContentTypeIdentifierCriterion": "article" } } } } ``` # LogicalOr Criterion The [`LogicalOr` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-LogicalOr.html) matches content if at least one of the provided Criteria matches. When querying for [products](https://doc.ibexa.co/en/latest/product_catalog/product_api/index.md), use [LogicalOr](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-Criterion-LogicalOr.html) instead. ## Arguments - `criterion` - a set of Criteria combined by the logical operator ## Example ### PHP ``` $query->filter = new Criterion\LogicalOr([ new Criterion\ContentTypeIdentifier('article'), new Criterion\SectionIdentifier(['sports', 'news']); ] ); ``` ### REST API **XML** ``` article news ``` **JSON** ``` { "Query": { "Filter": { "OR": { "ContentTypeIdentifierCriterion": "article", "SectionIdentifierCriterion": "news" } } } } ``` # Content Type Search Criteria reference Content Type Search Criteria are only supported by [Content Type Search (`ContentTypeService::findContentTypes`)](https://doc.ibexa.co/en/latest/content_management/content_api/managing_content/#finding-and-filtering-content-types). | Criterion | Description | | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | | [ContainsFieldDefinitionId](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-ContentType-Query-Criterion-ContainsFieldDefinitionId.html) | Matches content types that contain a field definition with the specified ID. | | [ContentTypeGroupId](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-ContentType-Query-Criterion-ContentTypeGroupId.html) | Matches content types by their assigned group ID. | | [ContentTypeGroupName](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-ContentType-Query-Criterion-ContentTypeGroupName.html) | Matches content types by the name of their assigned group. | | [ContentTypeId](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-ContentType-Query-Criterion-ContentTypeId.html) | Matches content types by their ID. | | [ContentTypeIdentifier](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-ContentType-Query-Criterion-ContentTypeIdentifier.html) | Matches content types by their identifier. | | [IsSystem](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-ContentType-Query-Criterion-IsSystem.html) | Matches content types based on whether the group they belong to is system or not. | | [LogicalAnd](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-ContentType-Query-Criterion-LogicalAnd.html) | Implements a logical AND Criterion. It matches if ALL of the provided Criteria match. | | [LogicalOr](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-ContentType-Query-Criterion-LogicalOr.html) | Implements a logical OR Criterion. It matches if at least one of the provided Criteria matches. | | [LogicalNot](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-ContentType-Query-Criterion-LogicalNot.html) | Implements a logical NOT Criterion. It matches if the provided Criterion doesn't match. | The following example shows how to use them to search for content types: ``` contentTypeService->findContentTypes($query); $output->writeln('Found ' . $searchResult->getTotalCount() . ' content type(s):'); foreach ($searchResult->getContentTypes() as $contentType) { $output->writeln(sprintf( '- [%d] %s (identifier: %s)', $contentType->id, $contentType->getName(), $contentType->identifier )); } return Command::SUCCESS; } } ``` # Product Search Criteria reference Product Search Criteria are supported by [product and product variant search](https://doc.ibexa.co/en/latest/product_catalog/product_api/#products) with the following methods: - `ProductServiceInterface::findProducts()` - `ProductServiceInterface::findProductVariants()` - `ProductServiceInterface::findVariants()` Search Criterion let you filter product by specific attributes, for example, color, availability, or price. ## Product Search Criteria To query for products coming from Quable PIM, see [Quable Search API](https://doc.ibexa.co/en/latest/product_catalog/quable/quable_api/#search-for-products) for details about the integration. | Search Criterion | Search based on | Local product catalog | Quable PIM | | ------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------- | --------------------- | ---------- | | [AttributeGroupIdentifier](https://doc.ibexa.co/en/latest/search/criteria_reference/attributegroupidentifier_criterion/index.md) | Value of product's attribute group identifier | Yes | | | [AttributeName](https://doc.ibexa.co/en/latest/search/criteria_reference/attributename_criterion/index.md) | Value of product's attribute name | Yes | | | [BasePrice](https://doc.ibexa.co/en/latest/search/criteria_reference/baseprice_criterion/index.md) | Product's base price | Yes | | | [CatalogIdentifier](https://doc.ibexa.co/en/latest/search/criteria_reference/catalogidentifier_criterion/index.md) | Catalog's identifier | Yes | | | [CatalogName](https://doc.ibexa.co/en/latest/search/criteria_reference/catalogname_criterion/index.md) | Catalog's name | Yes | | | [CatalogStatus](https://doc.ibexa.co/en/latest/search/criteria_reference/catalogstatus_criterion/index.md) | Catalog's status | Yes | | | [CheckboxAttribute](https://doc.ibexa.co/en/latest/search/criteria_reference/checkboxattribute_criterion/index.md) | Value of product's checkbox attribute | Yes | | | [ColorAttribute](https://doc.ibexa.co/en/latest/search/criteria_reference/colorattribute_criterion/index.md) | Value of product's color attribute | Yes | | | [CreatedAt](https://doc.ibexa.co/en/latest/search/criteria_reference/createdat_criterion/index.md) | Date and time when product was created | Yes | Yes | | [CreatedAtRange](https://doc.ibexa.co/en/latest/search/criteria_reference/createdatrange_criterion/index.md) | Date and time range when product was created | Yes | | | [CustomPrice](https://doc.ibexa.co/en/latest/search/criteria_reference/customprice_criterion/index.md) | Product's custom price | Yes | | | [DateTimeAttribute](https://doc.ibexa.co/en/latest/search/criteria_reference/datetimeattribute_criterion/index.md) | Value of product's date and time attribute | Yes | | | [DateTimeAttributeRange](https://doc.ibexa.co/en/latest/search/criteria_reference/datetimeattributerange_criterion/index.md) | Value of product's date and time attribute and given time range | Yes | | | [FloatAttribute](https://doc.ibexa.co/en/latest/search/criteria_reference/floatattribute_criterion/index.md) | Value of product's float attribute | Yes | | | [FloatAttributeRange](https://doc.ibexa.co/en/latest/search/criteria_reference/floatattributerange_criterion/index.md) | Value of product's float attribute | Yes | | | [IntegerAttribute](https://doc.ibexa.co/en/latest/search/criteria_reference/integerattribute_criterion/index.md) | Value of product's integer attribute | Yes | | | [IntegerAttributeRange](https://doc.ibexa.co/en/latest/search/criteria_reference/integerattributerange_criterion/index.md) | Value of product's integer attribute | Yes | | | [IsVirtual](https://doc.ibexa.co/en/latest/search/criteria_reference/isvirtual_criterion/index.md) | Product type (virtual or physical) | Yes | | | [LogicalAnd](https://doc.ibexa.co/en/latest/search/criteria_reference/logicaland_criterion/index.md) | Composite criterion to group multiple criteria using the AND condition | Yes | Yes | | [LogicalOr](https://doc.ibexa.co/en/latest/search/criteria_reference/logicalor_criterion/index.md) | Composite criterion to group multiple criteria using the OR condition | Yes | | | [MatchAll](https://doc.ibexa.co/en/latest/search/criteria_reference/matchall_criterion/index.md) | All products | Yes | Yes | | [ProductAvailability](https://doc.ibexa.co/en/latest/search/criteria_reference/productavailability_criterion/index.md) | Product's availability | Yes | | | [ProductCategory](https://doc.ibexa.co/en/latest/search/criteria_reference/productcategory_criterion/index.md) | Product category assigned to product | Yes | Yes | | [ProductCategorySubtree](https://doc.ibexa.co/en/latest/search/criteria_reference/productcategorysubtree_criterion/index.md) | Product category subtree assigned to product | Yes | Yes | | [ProductCode](https://doc.ibexa.co/en/latest/search/criteria_reference/productcode_criterion/index.md) | Product's code | Yes | Yes | | [ProductName](https://doc.ibexa.co/en/latest/search/criteria_reference/productname_criterion/index.md) | Product's name | Yes | Yes | | [ProductStock](https://doc.ibexa.co/en/latest/search/criteria_reference/productstock_criterion/index.md) | Product's numerical stock | Yes | | | [ProductStockRange](https://doc.ibexa.co/en/latest/search/criteria_reference/productstockrange_criterion/index.md) | Product's numerical stock | Yes | | | [ProductType](https://doc.ibexa.co/en/latest/search/criteria_reference/producttype_criterion/index.md) | Product type | Yes | Yes | | [RangeMeasurementAttributeMaximum](https://doc.ibexa.co/en/latest/search/criteria_reference/rangemeasurementattributemaximum_criterion/index.md) | Maximum value of product's measurement range attribute | Yes | | | [RangeMeasurementAttributeMinimum](https://doc.ibexa.co/en/latest/search/criteria_reference/rangemeasurementattributeminimum_criterion/index.md) | Minimum value of product's measurement range attribute | Yes | | | [SelectionAttribute](https://doc.ibexa.co/en/latest/search/criteria_reference/selectionattribute_criterion/index.md) | Value of product's selection attribute | Yes | | | [SimpleMeasurementAttribute](https://doc.ibexa.co/en/latest/search/criteria_reference/simplemeasurementattribute_criterion/index.md) | Value of product's single measurement attribute | Yes | | | [SymbolAttribute](https://doc.ibexa.co/en/latest/search/criteria_reference/symbolattribute_criterion/index.md) | Value of product's symbol attribute | Yes | | | [UpdatedAt](https://doc.ibexa.co/en/latest/search/criteria_reference/updated_at_criterion/index.md) | Product modification date | Yes | Yes | | [UpdatedAtRange](https://doc.ibexa.co/en/latest/search/criteria_reference/updated_at_range_criterion/index.md) | Product modification date range | Yes | | # AttributeName Criterion The `AttributeName` Search Criterion searches for products by the value of their attribute name. ## Arguments - `value` - string representing the attribute's name ## Example ### REST API **XML** ``` measure ``` **JSON** ``` { "AttributeQuery": { "Query": { "AttributeNameCriterion": "measure" } } } ``` # AttributeGroupIdentifier Criterion The `AttributeGroupIdentifier` Search Criterion searches for products by the value of their attribute group identifier. ## Arguments - `value` - string representing the attribute's identifier ## Example ### REST API **XML** ``` attribute_group ``` **JSON** ``` { "AttributeQuery": { "Query": { "AttributeGroupIdentifier": "attribute_group" } } } ``` # BasePrice Criterion The [`BasePrice` Search Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-Criterion-BasePrice.html) searches for products by their base price. ## Arguments - `value` - a `Money\Money` object representing the price in a specific currency - (optional) `operator` - Operator constant (EQ, GT, GTE, LT, LTE, default EQ) ## Limitations The `BasePrice` Criterion isn't available in the Legacy Search engine. ## Example ### PHP ``` $query = new ProductQuery( null, new \Ibexa\Contracts\ProductCatalog\Values\Product\Query\Criterion\BasePrice( \Money\Money::EUR(12900), \Ibexa\Contracts\ProductCatalog\Values\Product\Query\Criterion\Operator::GTE ) ); ``` # CatalogIdentifier Criterion The `CatalogIdentifier` Search Criterion searches for a catalog by the value of its identifier. ## Arguments - `value` - string representing the catalog's identifier ## Example ### REST API **XML** ``` catalog_1 ``` **JSON** ``` { "CatalogQuery": { "Query": { "CatalogIdentifierCriterion": "catalog_1", } } } ``` # CatalogName Criterion The `CatalogName` Search Criterion searches for catalogs by the value of their name. ## Arguments - `value` - string representing the catalog's name ## Example ### REST API **XML** ``` Furniture ``` **JSON** ``` { "CatalogQuery": { "Query": { "CatalogNameCriterion": "Furniture" } } } ``` # CatalogStatus Criterion The `CatalogStatus` Search Criterion searches for catalogs by the value of their status. ## Arguments - `value` - string representing the catalog's status ## Example ### REST API **XML** ``` published ``` **JSON** ``` { "CatalogQuery": { "Query": { "CatalogStatusCriterion": "published" } } } ``` # CheckboxAttribute Criterion The `CheckboxAttribute` Search Criterion searches for products by the value of their checkbox attribute. ## Arguments - `identifier` - string representing the attribute - `value` - bool representing the attribute value ## Example ### PHP ``` $query = new ProductQuery( null, new \Ibexa\Contracts\ProductCatalog\Values\Product\Query\Criterion\CheckboxAttribute('automatic', true) ); ``` # ColorAttribute Criterion The `ColorAttribute` Search Criterion searches for products by the value of their color attribute. ## Arguments - `identifier` - string representing the attribute - `value` - array of strings representing the attribute values ## Example ### PHP ``` $query = new ProductQuery( null, new \Ibexa\Contracts\ProductCatalog\Values\Product\Query\Criterion\ColorAttribute('color', ['#FF0000']) ); ``` ### REST API **XML** ``` color #000000 ``` **JSON** ``` { "AttributeQuery": { "Query": { "ColorAttributeCriterion": { "identifier": "color", "value": ["#000000"] }, } } } ``` # CreatedAt Criterion The `CreatedAt` Search Criterion searches for products based on the date when they were created. ## Arguments - `createdAt` (PHP), `created_at` (REST) - indicating the date that should be matched, provided as a `DateTimeInterface` object in PHP, or as a string acceptable by `DateTime` constructor in REST - `operator` - Operator constant (EQ, GT, GTE, LT, LTE) in PHP or its value in REST ## Example ### PHP ``` $criteria = new \Ibexa\Contracts\ProductCatalog\Values\Product\Query\Criterion\CreatedAt( new DateTime('2023-03-01'), \Ibexa\Contracts\ProductCatalog\Values\Product\Query\Criterion\Operator::GTE, ); $productQuery = new ProductQuery(null, $criteria); ``` ### REST API **XML** ``` 2023-06-12 >= ``` **JSON** ``` { "ProductQuery": { "Filter": { "CreatedAtCriterion": { "created_at": "2023-06-12", "operator": ">=" } } } } ``` # CreatedAtRange Criterion The `CreatedAtRange` Search Criterion searches for products based on the date range when they were created. ## Arguments - `min` - indicating the beginning of the date range, provided as a `DateTimeInterface` object - `max` - indicating the end of the date range, provided as a `DateTimeInterface` object ## Example ### PHP ``` $criteria = new \Ibexa\Contracts\ProductCatalog\Values\Product\Query\Criterion\CreatedAtRange( new \DateTimeImmutable('2020-07-10T00:00:00+00:00'), new \DateTimeImmutable('2023-07-12T00:00:00+00:00') ); $productQuery = new ProductQuery(null, $criteria); ``` ### REST API **XML** ``` 2023-06-12 2023-06-20 ``` **JSON** ``` { "ProductQuery": { "Filter": { "CreatedAtRange": { "min": "2023-06-12", "max": "2023-06-20" } } } } ``` # CustomPrice Criterion The `CustomPrice` Search Criterion searches for products by their custom price for a specific customer group. ## Arguments - `value` - a `Money\Money` object representing the price in a specific currency - (optional) `operator` - Operator constant (EQ, GT, GTE, LT, LTE, default EQ) - (optional) `customerGroup` - a `CustomerGroupInterface` object representing the customer group to show prices for. If you don't provide a customer group, the query uses the group related to the current user. ## Limitations The `CustomPrice` Criterion isn't available in the Legacy Search engine. ## Example ### PHP ``` $query = new ProductQuery( null, new \Ibexa\Contracts\ProductCatalog\Values\Product\Query\Criterion\CustomPrice( \Money\Money::EUR(13800), \Ibexa\Contracts\ProductCatalog\Values\Product\Query\Criterion\Operator::GTE, $customerGroup) ); ``` # DateTimeAttribute criterion The [`DateTimeAttribute Search Criterion`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalogDateTimeAttribute-Search-Criterion-DateTimeAttribute.html) searches for products by value of a specified attribute, based on the [date and time attribute](https://doc.ibexa.co/en/latest/product_catalog/attributes/date_and_time/index.md) type. ## Arguments - `identifier` - attribute's identifier (string) - `value` - searched value ([DateTimeImmutable](https://www.php.net/manual/en/class.datetimeimmutable.php)) ## Operators The following operators are supported: - [FieldValueCriterion::COMPARISON_EQ](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-CoreSearch-Values-Query-Criterion-FieldValueCriterion.html#constant_COMPARISON_EQ) - [FieldValueCriterion::COMPARISON_NEQ](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-CoreSearch-Values-Query-Criterion-FieldValueCriterion.html#constant_COMPARISON_NEQ) - [FieldValueCriterion::COMPARISON_LT](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-CoreSearch-Values-Query-Criterion-FieldValueCriterion.html#constant_COMPARISON_LT) - [FieldValueCriterion::COMPARISON_LTE](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-CoreSearch-Values-Query-Criterion-FieldValueCriterion.html#constant_COMPARISON_LTE) - [FieldValueCriterion::COMPARISON_GT](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-CoreSearch-Values-Query-Criterion-FieldValueCriterion.html#constant_COMPARISON_GT) - [FieldValueCriterion::COMPARISON_GTE](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-CoreSearch-Values-Query-Criterion-FieldValueCriterion.html#constant_COMPARISON_GTE) ## Example ### PHP The following example lists all products for which the `event_date` attribute has value equal to 2025-07-06. ``` setOperator(FieldValueCriterion::COMPARISON_EQ); $query->setFilter($filter); /** @var \Ibexa\Contracts\ProductCatalog\ProductServiceInterface $productService */ $results = $productService->findProducts($query); ``` # DateTimeAttributeRange criterion The [`DateTimeAttributeRange Search Criterion`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalogDateTimeAttribute-Search-Criterion-DateTimeAttributeRange.html) searches for products by value of a specified attribute, which must be based on the [date and time attribute](https://doc.ibexa.co/en/latest/product_catalog/attributes/date_and_time/index.md) type. ## Arguments - `identifier` - attribute's identifier (string) - `min` - lower range value (inclusive) of [DateTimeImmutable](https://www.php.net/manual/en/class.datetimeimmutable.php) type. Optional. - `max` - upper range value (inclusive) of [DateTimeImmutable](https://www.php.net/manual/en/class.datetimeimmutable.php) type. Optional. ## Example ### PHP The following example lists all products for which the `event_date` attribute has value greater than 2025-01-01. ``` setFilter(new DateTimeAttributeRange('event_date', new DateTimeImmutable('2025-01-01'))); /** @var \Ibexa\Contracts\ProductCatalog\ProductServiceInterface $productService */ $results = $productService->findProducts($query); ``` # FloatAttribute Criterion The `FloatAttribute` Search Criterion searches for products by the value of their float attribute. ## Arguments - `identifier` - string representing the attribute - `value` - string representing the attribute value ## Example ### PHP ``` $query = new ProductQuery( null, new \Ibexa\Contracts\ProductCatalog\Values\Product\Query\Criterion\FloatAttribute( 'length', 16.5 ) ); ``` ### REST API **XML** ``` length 16.5 ``` **JSON** ``` { "AttributeQuery": { "Query": { "FloatAttributeCriterion": { "identifier": "length", "value": 16.5 } } } } ``` # FloatAttributeRange Criterion The `FloatAttributeRange` Search Criterion searches for products by the range of values of their float attribute. ## Arguments - `identifier` - string representing the attribute - `min` - indicating the beginning of the range - `max` - indicating the end of the date range ## Example ### REST API **XML** ``` length 16.5 25 ``` **JSON** ``` { "AttributeQuery": { "Query": { "FloatAttributeRangeCriterion": { "identifier": "length", "min": 16.5, "max": 25 } } } } ``` # IntegerAttribute Criterion The `IntegerAttribute` Search Criterion searches for products by the value of their integer attribute. ## Arguments - `identifier` - string representing the attribute - `value` - string representing the attribute value ## Example ### PHP ``` $query = new ProductQuery( null, new \Ibexa\Contracts\ProductCatalog\Values\Product\Query\Criterion\IntegerAttribute( 'size', 38 ) ); ``` ### REST API **XML** ``` size 38 ``` **JSON** ``` { "AttributeQuery": { "Query": { "IntegerAttributeCriterion": { "identifier": "size", "value": 38 } } } } ``` # IntegerAttributeRange Criterion The `IntegerAttributeRange` Search Criterion searches for products by the range of values of their integer attribute. ## Arguments - `identifier` - string representing the attribute - `min` - indicating the beginning of the range - `max` - indicating the end of the date range ## Example ### REST API **XML** ``` length 16 25 ``` **JSON** ``` { "AttributeQuery": { "Query": { "IntegerAttributeRangeCriterion": { "identifier": "length", "min": 16, "max": 25 } } } } ``` # IsVirtual Criterion The `IsVirtual` Search Criterion searches for virtual or physical products. ## Arguments - (optional) `isVirtual` - bool representing whether to search for virtual (default `true`) or physical (`false`) products. ## Example ### PHP ``` $query = new ProductQuery( null, new \Ibexa\Contracts\ProductCatalog\Values\Product\Query\Criterion\IsVirtual(true) ); ``` ### REST API **XML** ``` true ``` **JSON** ``` "ProductQuery": { "Filter": { "IsVirtualCriterion": true } } ``` # ProductAvailability Criterion The `ProductAvailability` Search Criterion searches for products by their availability. ## Arguments - (optional) `productAvailability` - bool representing whether the product is available (default `true`) ## Example ### PHP ``` $query = new ProductQuery( null, new \Ibexa\Contracts\ProductCatalog\Values\Product\Query\Criterion\ProductAvailability(true) ); ``` ### REST API **XML** ``` false ``` **JSON** ``` { "ProductQuery": { "Filter": { "ProductAvailabilityCriterion": false } } } ``` # ProductStock Criterion The `ProductStock` Search Criterion searches for products by their numerical stock. ## Arguments - `value` - the numerical stock to search for - (optional) `operator` - operator string (`=` `<` `<=` `>` `>=`) ## Example ### PHP ``` $productQuery = new ProductQuery( null, new Criterion\ProductStock(10) ); ``` ``` $productQuery = new ProductQuery( null, new Criterion\ProductStock(50, '>=') ); ``` # ProductStockRange Criterion The `ProductStockRange` Search Criterion searches for products by their numerical stock. ## Arguments - `min` - minimum stock - `max` - maximum stock ## Example ### PHP ``` $productQuery = new ProductQuery( null, new Criterion\ProductStockRange(10, 120) ); ``` # ProductCategory Criterion The `ProductCategory` Search Criterion searches for products by the category they're assigned to. ## Arguments - `taxonomyEntries` - array of ints representing category IDs ## Example ### PHP ``` $query = new ProductQuery( null, new \Ibexa\Contracts\ProductCatalog\Values\Product\Query\Criterion\ProductCategory([2, 3]) ); ``` ### REST API **XML** ``` [2, 3] ``` **JSON** ``` { "ProductQuery": { "Filter": { "ProductCategoryCriterion": [ 2, 3 ] } } } ``` # ProductCategorySubtree Criterion The `ProductCategorySubtree` Search Criterion searches for products assigned to a given product category or any of its subcategories. Unlike the [`ProductCategory` criterion](https://doc.ibexa.co/en/latest/search/criteria_reference/productcategory_criterion/index.md), which matches products assigned to specific category IDs, `ProductCategorySubtree` matches the entire subtree rooted at the provided category, including all descendant categories. ## Arguments - `taxonomyEntryId` - int representing the ID of the root taxonomy entry (product category) of the subtree to search within ## Example ### PHP ``` setQuery($criteria); $results = $productService->findProducts($productQuery); ``` # ProductCode Criterion The `ProductCode` Search Criterion searches for products by their codes. ## Arguments - `productCode` - array of strings representing the product codes(s) ## Example ### PHP ``` $query = new ProductQuery( null, new \Ibexa\Contracts\ProductCatalog\Values\Product\Query\Criterion\ProductCode(['ergo_desk', 'alter_desk']) ); ``` ### REST API **XML** ``` ski snowboard ``` **JSON** ``` { "ProductQuery": { "Filter": { "ProductCodeCriterion": [ "ski", "snowboard" ] } } } ``` # ProductName Criterion The `ProductName` Search Criterion searches for products by their names. ## Arguments - `productName` - string representing the Product name, with `*` as wildcard ## Example ### PHP ``` $query = new ProductQuery( null, new \Ibexa\Contracts\ProductCatalog\Values\Product\Query\Criterion\ProductName('sofa*') ); ``` ### REST API **XML** ``` sofa* ``` **JSON** ``` { "ProductQuery": { "Filter": { "ProductNameCriterion": "sofa*" } } } ``` # ProductType Criterion The `ProductType` Search Criterion searches for products by their codes. ## Arguments - `productType` - array of strings representing the product type(s) ## Example ### PHP ``` $query = new ProductQuery( null, new \Ibexa\Contracts\ProductCatalog\Values\Product\Query\Criterion\ProductType(['dress']) ); ``` ### REST API **XML** ``` desk ``` **JSON** ``` { "ProductQuery": { "Filter": { "ProductTypeCriterion": "desk" } } } ``` # RangeMeasurementAttributeMinimum Criterion The `RangeMeasurementAttributeMinimum` Search Criterion searches for products by the minimum value of their measurement (range) attribute. ## Arguments - `identifier` - string representing the attribute - `value` - `\Ibexa\Contracts\Measurement\Value\SimpleValueInterface` object representing the minimum attribute value ## Example ### PHP ``` $value = $this->measurementService->buildSimpleValue('length', 100, 'centimeter'); $query = new ProductQuery( null, new \Ibexa\Contracts\ProductCatalog\Values\Product\Query\Criterion\RangeMeasurementAttributeMinimum( 'length', $value ) ); ``` # RangeMeasurementAttributeMaximum Criterion The `RangeMeasurementAttributeMaximum` Search Criterion searches for products by the maximum value of their measurement (range) attribute. ## Arguments - `identifier` - string representing the attribute - `value` - `\Ibexa\Contracts\Measurement\Value\SimpleValueInterface` object representing the maximum attribute value ## Example ### PHP ``` $value = $this->measurementService->buildSimpleValue('length', 150, 'centimeter'); $query = new ProductQuery( null, new \Ibexa\Contracts\ProductCatalog\Values\Product\Query\Criterion\RangeMeasurementAttributeMaximum( 'length', $value ) ); ``` # SimpleMeasurementAttribute Criterion The `SimpleMeasurementAttribute` Search Criterion searches for products by the value of their measurement (single) attribute. ## Arguments - `identifier` - string representing the attribute - `value` - `Ibexa\Contracts\Measurement\Value\SimpleValueInterface` object representing the attribute value ## Example ### PHP ``` $value = $this->measurementService->buildSimpleValue('length', 120, 'centimeter'); $query = new ProductQuery( null, new \Ibexa\Contracts\ProductCatalog\Values\Product\Query\Criterion\SimpleMeasurementAttribute( 'width', $value ) ); ``` # SelectionAttribute Criterion The `SelectionAttribute` Search Criterion searches for products by the value of their selection attribute. ## Arguments - `identifier` - string representing the attribute - `value` - array of strings representing the attribute values ## Example ### PHP ``` $query = new ProductQuery( null, new \Ibexa\Contracts\ProductCatalog\Values\Product\Query\Criterion\SelectionAttribute( 'fabric_type', ['cotton'] ) ); ``` ### REST API **XML** ``` fabric_type [cotton] ``` **JSON** ``` { "AttributeQuery": { "Query": { "SelectionAttributeCriterion": { "identifier": "fabric_type", "value": [ "cotton" ] } } } } ``` # SymbolAttributeCriterion The `SymbolAttribute` Search Criterion searches for products by [symbol attribute](https://doc.ibexa.co/en/latest/product_catalog/attributes/symbol_attribute_type/index.md). ## Arguments - `identifier` - identifier of the format - `value` - array with the values to search for ## Example ### PHP ``` setFilter(new SymbolAttribute('ean', ['5023920187205'])); /** @var \Ibexa\Contracts\ProductCatalog\ProductServiceInterface $productService */ $results = $productService->findProducts($query); ``` # UpdatedAt Criterion The `UpdatedAt` Search Criterion searches for products based on the date when they were last updated. ## Arguments - `date` - indicating the date that should be matched, provided as a [`DateTimeInterface`](https://www.php.net/manual/en/class.datetimeinterface.php) object in PHP, or as a string acceptable by `DateTimeInterface` constructor in REST - `operator` - Operator constant (EQ, GT, GTE, LT, LTE) in PHP or its value in REST ## Operators | Operator | Value | Description | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | ------------------------------------------------------------ | | [`Operator::EQ`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-Criterion-Operator.html#constant_EQ) | `=` | Matches products updated exactly on the given date (default) | | [`Operator::GT`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-Criterion-Operator.html#constant_GT) | `>` | Matches products updated after the given date | | [`Operator::GTE`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-Criterion-Operator.html#constant_GTE) | `>=` | Matches products updated on or after the given date | | [`Operator::LT`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-Criterion-Operator.html#constant_LT) | `<` | Matches products updated before the given date | | [`Operator::LTE`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-Criterion-Operator.html#constant_LTE) | `<=` | Matches products updated on or before the given date | ## Example ### PHP ``` setQuery($criteria); $results = $productService->findProducts($productQuery); ``` ### REST API **XML** ``` 2023-06-12 >= ``` **JSON** ``` { "ProductQuery": { "Filter": { "UpdatedAtCriterion": { "updated_at": "2023-06-12", "operator": ">=" } } } } ``` # UpdatedAtRange Criterion The `UpdatedAtRange` Search Criterion searches for products based on the date range when they were last updated. ## Arguments - `min` - the start of the date range (inclusive), provided as a [`DateTimeInterface`](https://www.php.net/manual/en/class.datetimeinterface.php) object in PHP, or as a string acceptable by `DateTimeInterface` constructor in REST - `max` - the end of the date range (inclusive), provided as a [`DateTimeInterface`](https://www.php.net/manual/en/class.datetimeinterface.php) object in PHP, or as a string acceptable by `DateTimeInterface` constructor in REST At least one of `min` or `max` must be provided. ## Example ### PHP ``` setQuery($criteria); $results = $productService->findProducts($productQuery); ``` ### REST API **XML** ``` 2023-06-12 2023-06-20 ``` **JSON** ``` { "ProductQuery": { "Filter": { "UpdatedAtRangeCriterion": { "min": "2023-06-12", "max": "2023-06-20" } } } } ``` # Order Search Criteria reference Editions: Commerce Order Search Criteria are only supported by [Order Search (`OrderService::findOrders`)](https://doc.ibexa.co/en/latest/commerce/order_management/order_management_api/#get-multiple-orders). With these Criteria you can filter orders, for example, by their order identifier, order creation date, order status, customer name, or customer status. ## Order Search Criteria | Search Criterion | Search based on | | ------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------- | | [CompanyNameCriterion](https://doc.ibexa.co/en/latest/search/criteria_reference/order_company_name_criterion/index.md) | Name of the company | | [CreatedAtCriterion](https://doc.ibexa.co/en/latest/search/criteria_reference/order_created_criterion/index.md) | Date and time when order was created | | [CurrencyCodeCriterion](https://doc.ibexa.co/en/latest/search/criteria_reference/order_currency_code_criterion/index.md) | Currency code | | [CustomerNameCriterion](https://doc.ibexa.co/en/latest/search/criteria_reference/order_customer_name_criterion/index.md) | Customer's user name | | [IdentifierCriterion](https://doc.ibexa.co/en/latest/search/criteria_reference/order_identifier_criterion/index.md) | Order identifier | | [IsCompanyAssociatedCriterion](https://doc.ibexa.co/en/latest/search/criteria_reference/order_company_associated_criterion/index.md) | Whether the customer represents a company | | [OwnerCriterion](https://doc.ibexa.co/en/latest/search/criteria_reference/order_owner_criterion/index.md) | Owner based on the user reference | | [PriceCriterion](https://doc.ibexa.co/en/latest/search/criteria_reference/order_price_criterion/index.md) | Total value of the order | | [SourceCriterion](https://doc.ibexa.co/en/latest/search/criteria_reference/order_source_criterion/index.md) | Source of the order | | [StatusCriterion](https://doc.ibexa.co/en/latest/search/criteria_reference/order_status_criterion/index.md) | Status of the order | # Order CompanyName Criterion Editions: Commerce The `CompanyNameCriterion` Search Criterion searches for orders based on the name of the company. ## Arguments - `company_name` - string that represents a name of the company ## Example ### PHP ``` $query = new OrderQuery( new \Ibexa\Contracts\OrderManagement\Value\Order\Query\Criterion\CompanyNameCriterion('IBM') ); ``` # Order CreatedAt Criterion Editions: Commerce The `CreatedAtCriterion` Search Criterion searches for orders based on the date when they were created. ## Arguments - `createdAt` - date to be matched, provided as a `DateTimeInterface` object - `operator` - optional operator string (EQ, GT, GTE, LT, LTE) ## Example ### PHP ``` $criteria = new \Ibexa\Contracts\OrderManagement\Value\Order\Query\Criterion\CreatedAtCriterion( new DateTime('2023-03-01'), 'GTE' ); $orderQuery = new OrderQuery($criteria); ``` # Order CurrencyCode Criterion Editions: Commerce The `CurrencyCodeCriterion` Search Criterion searches for orders based on the currency code. ## Arguments - `currency_code` - string that represents a currency code ## Example ### PHP ``` $query = new OrderQuery( new \Ibexa\Contracts\OrderManagement\Value\Order\Query\Criterion\CurrencyCodeCriterion('USD') ); ``` # Order CustomerName Criterion Editions: Commerce The `CustomerNameCriterion` Search Criterion searches for orders based on the name of the customer. ## Arguments - `user_name` - string that represents a name of the customer ## Example ### PHP ``` $query = new OrderQuery( new \Ibexa\Contracts\OrderManagement\Value\Order\Query\Criterion\CustomerNameCriterion('john') ); ``` # Order Identifier Criterion Editions: Commerce The `IdentifierCriterion` Search Criterion searches for orders based on the order identifier. ## Arguments - `identifier` - string that represents the order identifier ## Example ### PHP ``` $query = new OrderQuery( new \Ibexa\Contracts\OrderManagement\Value\Order\Query\Criterion\IdentifierCriterion('f7578972-e7f4-4cae-85dc-a7c74610204e') ); ``` # Order IsCompanyAssociated Criterion Editions: Commerce The `IsCompanyAssociatedCriterion` Search Criterion searches for orders based on whether the customer represents a business company. ## Arguments - `value` - boolean that shows whether the customer represents a business company ## Example ### PHP ``` $query = new OrderQuery( new \Ibexa\Contracts\OrderManagement\Value\Order\Query\Criterion\IsCompanyAssociatedCriterion(true) ); ``` # Owner Criterion Editions: Commerce The `OwnerCriterion` Criterion searches for orders based on the user reference. ## Arguments - `UserReference` object - \\Ibexa\\Contracts\\Core\\Repository\\Values\\User\\UserReference(int $userId) ## Example ``` $query = new OrderQuery( new \Ibexa\Contracts\OrderManagement\Value\Order\Query\Criterion\OwnerCriterion( \Ibexa\Contracts\Core\Repository\Values\User\UserReference(14) ) ); ``` `OwnerCriterion` Criterion accepts also multiple values: ``` $query = new OrderQuery( new \Ibexa\Contracts\OrderManagement\Value\Order\Query\Criterion\OwnerCriterion( [ \Ibexa\Contracts\Core\Repository\Values\User\UserReference(14), \Ibexa\Contracts\Core\Repository\Values\User\UserReference(123), ] ) ); ``` # Order Price Criterion Editions: Commerce The `PriceCriterion` searches for orders by their total net value. ## Arguments - `value` - value to be matched, represents total net order value - (optional) `operator` - optional operator string (EQ, GT, GTE, LT, LTE) ## Example ### PHP ``` $criteria = new \Ibexa\Contracts\OrderManagement\Value\Order\Query\Criterion\PriceCriterion( 12900, 'GTE' ); $orderQuery = new OrderQuery($criteria); ``` # Order Source Criterion Editions: Commerce The `SourceCriterion` Search Criterion searches for orders based on the source of the order. ## Arguments - `source` - string that represents the source of the order ## Example ### PHP ``` $query = new OrderQuery( new \Ibexa\Contracts\OrderManagement\Value\Order\Query\Criterion\SourceCriterion('local_shop') ); ``` # Order Status Criterion Editions: Commerce The `StatusCriterion` Search Criterion searches for orders based on order status. ## Arguments - `status` - string that represents the status of the order, takes values defined in order management workflow ## Example ### PHP ``` $query = new OrderQuery( new \Ibexa\Contracts\OrderManagement\Value\Order\Query\Criterion\StatusCriterion('pending') ); ``` # Payment Search Criteria reference Editions: Commerce Payment Search Criteria are only supported by [Payment Search (`PaymentServiceInterface::findPayments`)](https://doc.ibexa.co/en/latest/commerce/payment/payment_api/#get-multiple-payments). With these Criteria you can filter payments by their payment identifier, payment creation date, payment status, payment method, order, and more. ## Payment Search Criteria | Search Criterion | Search based on | | ------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | | [CreatedAt](https://doc.ibexa.co/en/latest/search/criteria_reference/payment_createdat_criterion/index.md) | Date and time when payment was created | | [Currency](https://doc.ibexa.co/en/latest/search/criteria_reference/payment_currency_criterion/index.md) | Currency code | | [Id](https://doc.ibexa.co/en/latest/search/criteria_reference/payment_id_criterion/index.md) | Payment ID | | [Identifier](https://doc.ibexa.co/en/latest/search/criteria_reference/payment_identifier_criterion/index.md) | Payment identifier | | [LogicalAnd](https://doc.ibexa.co/en/latest/search/criteria_reference/payment_logicaland_criterion/index.md) | Logical AND criterion that matches if all the provided Criteria match | | [LogicalOr](https://doc.ibexa.co/en/latest/search/criteria_reference/payment_logicalor_criterion/index.md) | Logical OR criterion that matches if at least one of the provided Criteria matches | | [Order](https://doc.ibexa.co/en/latest/search/criteria_reference/payment_order_criterion/index.md) | ID of an associated order | | [PaymentMethod](https://doc.ibexa.co/en/latest/search/criteria_reference/payment_payment_method_criterion/index.md) | Payment method applied to the payment | | [Status](https://doc.ibexa.co/en/latest/search/criteria_reference/payment_status_criterion/index.md) | Status of the payment | | [UpdatedAt](https://doc.ibexa.co/en/latest/search/criteria_reference/payment_updatedat_criterion/index.md) | Date and time when payment status was updated | # Payment CreatedAt Criterion Editions: Commerce The `CreatedAt` Search Criterion searches for payments based on the date when they were created. ## Arguments - `createdAt` - date to be matched, provided as a `DateTimeInterface` object - `operator` - optional operator string (EQ, GT, GTE, LT, LTE) ## Example ### PHP ``` $criteria = new \Ibexa\Contracts\Payment\Payment\Query\Criterion\CreatedAt( new DateTime('2023-03-01') ); $query = new PaymentQuery($criteria); ``` # Payment Currency Criterion Editions: Commerce The `Currency` Search Criterion searches for payments based on the currency code. ## Arguments - `currency` - string that represents a currency code ## Example ### PHP ``` $query->query = new \Ibexa\Contracts\Payment\Payment\Query\Criterion\Currency('EUR'); ``` # Payment Id Criterion Editions: Commerce The `Id` Search Criterion searches for payments based on the payment ID. ## Arguments - `id` - integer that represents the payment ID ## Example ### PHP ``` $query->query = new \Ibexa\Contracts\Payment\Payment\Query\Criterion\Id(2); ``` # Payment Identifier Criterion Editions: Commerce The `Identifier` Search Criterion searches for payments based on the payment identifier. ## Arguments - `identifier` - string that represents the payment identifier ## Example ### PHP ``` $query->query = new \Ibexa\Contracts\Payment\Payment\Query\Criterion\Identifier('f7578972-e7f4-4cae-85dc-a7c74610204e'); ``` # Payment LogicalAnd Criterion Editions: Commerce The `LogicalAnd` Search Criterion matches payments if all provided Criteria match. ## Arguments - `criterion` - a set of Criteria combined by the logical operator ## Example ### PHP ``` $query->query = new \Ibexa\Contracts\Payment\Payment\Query\Criterion\LogicalAnd( [ new \Ibexa\Contracts\Payment\Payment\Query\Criterion\CreatedAt(new DateTime('2023-03-01')); new \Ibexa\Contracts\Payment\Payment\Query\Criterion\Currency('USD'); ] ); ``` # Payment LogicalOr Criterion Editions: Commerce The `LogicalOr` Search Criterion matches payments if at least one of the provided Criteria matches. ## Arguments - `criterion` - a set of Criteria combined by the logical operator ## Example ### PHP ``` $query->query = new Criterion\LogicalOr( [ new \Ibexa\Contracts\Payment\Payment\Query\Criterion\CreatedAt(new DateTime('2023-03-01')); new \Ibexa\Contracts\Payment\Payment\Query\Criterion\Currency('USD'); ] ); ``` # Payment Order Criterion Editions: Commerce The `Order` Search Criterion searches for payments based on an ID of an associated order. ## Arguments - `order_id` - integer that represents an ID of an associated order ## Example ### PHP ``` $query->query = new \Ibexa\Contracts\Payment\Payment\Query\Criterion\Order(4); ``` # Payment PaymentMethod Criterion Editions: Commerce The `PaymentMethod` Search Criterion searches for payments based on a payment method applied to them. ## Arguments - `method_id` - integer that represents an ID of the payment method that you want to match ## Example ### PHP ``` $query->query = new \Ibexa\Contracts\Payment\Payment\Query\Criterion\PaymentMethod(2); ``` # Payment Status Criterion Editions: Commerce The `Status` Search Criterion searches for payments based on payment status. ## Arguments - `status` - string that represents the status of the payment, takes values defined in payment processing workflow ## Example ### PHP ``` $query->query = new \Ibexa\Contracts\Payment\Payment\Query\Criterion\Status('failed'); ``` # Payment UpdatedAt Criterion Editions: Commerce The `UpdatedAt` Search Criterion searches for payments based on the date when their status was updated. ## Arguments - `updatedAt` - date to be matched, provided as a `DateTimeInterface` object - `operator` - optional operator string (EQ, GT, GTE, LT, LTE) ## Example ### PHP ``` $criteria = new \Ibexa\Contracts\Payment\Payment\Query\Criterion\UpdatedAt( new DateTime('2023-03-01') ); $query = new PaymentQuery($criteria); ``` # Payment Method Search Criteria reference Editions: Commerce Payment Method Search Criteria are only supported by [Payment Method Search (`PaymentMethodService::findPaymentMethods`)](https://doc.ibexa.co/en/latest/commerce/payment/payment_method_api/#get-multiple-payment-methods). With these Criteria you can filter payment methods by their payment method identifier, payment method creation date, payment method type, status, and more. ## Payment method Search Criteria | Search Criterion | Search based on | | ------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | | [CreatedAt](https://doc.ibexa.co/en/latest/search/criteria_reference/payment_method_createdat_criterion/index.md) | Date and time when payment method was created | | [Enabled](https://doc.ibexa.co/en/latest/search/criteria_reference/payment_method_enabled_criterion/index.md) | Status of the payment method | | [Id](https://doc.ibexa.co/en/latest/search/criteria_reference/payment_method_id_criterion/index.md) | Payment method ID | | [Identifier](https://doc.ibexa.co/en/latest/search/criteria_reference/payment_method_identifier_criterion/index.md) | Payment method identifier | | [LogicalAnd](https://doc.ibexa.co/en/latest/search/criteria_reference/payment_method_logicaland_criterion/index.md) | Logical AND criterion that matches if all the provided Criteria match | | [LogicalOr](https://doc.ibexa.co/en/latest/search/criteria_reference/payment_method_logicalor_criterion/index.md) | Logical OR criterion that matches if at least one of the provided Criteria matches | | [Name](https://doc.ibexa.co/en/latest/search/criteria_reference/payment_method_name_criterion/index.md) | Payment method name | | [Type](https://doc.ibexa.co/en/latest/search/criteria_reference/payment_method_type_criterion/index.md) | Type of the payment method | | [UpdatedAt](https://doc.ibexa.co/en/latest/search/criteria_reference/payment_method_updatedat_criterion/index.md) | Date and time when payment method status was updated | # Payment Method CreatedAt Criterion Editions: Commerce The `CreatedAt` Search Criterion searches for payment methods based on the date when they were created. ## Arguments - `createdAt` - date to be matched, provided as a `DateTimeInterface` object - `operator` - optional operator string (EQ, GT, GTE, LT, LTE) ## Example ### PHP ``` $criteria = new \Ibexa\Contracts\Payment\PaymentMethod\Query\Criterion\CreatedAt( new DateTime('2023-03-01') ); $query = new PaymentMethodQuery($criteria); ``` # Payment Method Enabled Criterion Editions: Commerce The `Enabled` Search Criterion searches for payment methods based on whether the payment method is enabled or not. ## Arguments - `value` - whether the payment method is enabled ## Example ### PHP ``` $query->query = new \Ibexa\Contracts\Payment\PaymentMethod\Query\Criterion\Enabled(true); ``` # Payment Method Id Criterion Editions: Commerce The `Id` Search Criterion searches for payment methods based on the payment method ID. ## Arguments - `id` - integer that represents the payment method ID ## Example ### PHP ``` $query->query = new \Ibexa\Contracts\Payment\PaymentMethod\Query\Criterion\Id(2); ``` # Payment Method Identifier Criterion Editions: Commerce The `Identifier` Search Criterion searches for payment methods based on the payment method identifier. ## Arguments - `identifier` - string that represents the payment method identifier ## Example ### PHP ``` $query->query = new \Ibexa\Contracts\Payment\PaymentMethod\Query\Criterion\Identifier('f7578972-e7f4-4cae-85dc-a7c74610204e'); ``` # Payment Method LogicalAnd Criterion Editions: Commerce The `LogicalAnd` Search Criterion matches payment methods if all provided Criteria match. ## Arguments - `criteria` - a set of Criteria combined by the logical operator ## Example ### PHP ``` $query->query = new \Ibexa\Contracts\Payment\PaymentMethod\Query\Criterion\LogicalAnd( [ new \Ibexa\Contracts\Payment\PaymentMethod\Query\Criterion\CreatedAt(new DateTime('2023-03-01')); new \Ibexa\Contracts\Payment\PaymentMethod\Query\Criterion\Enabled(true); ] ); ``` # Payment Method LogicalOr Criterion Editions: Commerce The `LogicalOr` Search Criterion matches payment methods if at least one of the provided Criteria matches. ## Arguments - `criteria` - a set of Criteria combined by the logical operator ## Example ### PHP ``` $query->query = new \Ibexa\Contracts\Payment\PaymentMethod\Query\Criterion\LogicalOr( [ new \Ibexa\Contracts\Payment\PaymentMethod\Query\Criterion\CreatedAt(new DateTime('2023-03-01')); new \Ibexa\Contracts\Payment\PaymentMethod\Query\Criterion\CreatedAt(new DateTime('2023-05-01')); ] ); ``` # Payment Method Name Criterion Editions: Commerce The `Name` Search Criterion searches for payment methods based on the existing payment method name. ## Arguments - `name` - string that represents the payment method name ## Example ### PHP ``` $query->query = new \Ibexa\Contracts\Payment\PaymentMethod\Query\Criterion\Name('Credit Card'); ``` # Payment Method Type Criterion Editions: Commerce The `Type` Search Criterion searches for payment methods based on payment method type. ## Arguments - `type` - string that represents a payment method type ## Example ### PHP ``` $query->query = new \Ibexa\Contracts\Payment\PaymentMethod\Query\Criterion\Type('offline'); ``` # Payment Method UpdatedAt Criterion Editions: Commerce The `UpdatedAt` Search Criterion searches for payment methods based on the date when their status was updated. ## Arguments - `updatedAt` - date to be matched, provided as a `DateTimeInterface` object - `operator` - optional operator string (EQ, GT, GTE, LT, LTE) ## Example ### PHP ``` $criteria = new \Ibexa\Contracts\Payment\PaymentMethod\Query\Criterion\UpdatedAt( new DateTime('2023-03-01') ); $query = new PaymentMethodQuery($criteria); ``` # Price Search Criteria reference Price Search Criteria are only supported by [Price Search (`ProductPriceServiceInterface::findPrices`)](https://doc.ibexa.co/en/latest/product_catalog/price_api/#prices). With these Criteria you can filter prices by currency, customer group, product, and more. ## Price Search Criteria | Search Criterion | Search based on | | ---------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | | [Currency](https://doc.ibexa.co/en/latest/search/criteria_reference/price_currency_criterion/index.md) | Currency | | [CustomerGroup](https://doc.ibexa.co/en/latest/search/criteria_reference/price_customergroup_criterion/index.md) | A customer group that the price applies to | | [IsBasePrice](https://doc.ibexa.co/en/latest/search/criteria_reference/price_isbaseprice_criterion/index.md) | Boolean that indicates whether the price is a base price | | [IsCustomPrice](https://doc.ibexa.co/en/latest/search/criteria_reference/price_iscustomprice_criterion/index.md) | Boolean that indicates whether the price is a custom price | | [LogicalAnd](https://doc.ibexa.co/en/latest/search/criteria_reference/price_logicaland_criterion/index.md) | Logical AND criterion that matches if all the provided Criteria match | | [LogicalOr](https://doc.ibexa.co/en/latest/search/criteria_reference/price_logicalor_criterion/index.md) | Logical OR criterion that matches if at least one of the provided Criteria matches | | [Product](https://doc.ibexa.co/en/latest/search/criteria_reference/price_product_criterion/index.md) | Product code | # Price Currency Criterion The `Currency` Search Criterion searches for prices based on the given currency. ## Arguments - `currency` - a single object or an array of `CurrencyInterface` objects that represent the currency (`Ibexa\Contracts\ProductCatalog\Values\CurrencyInterface`) ## Example ### PHP ``` $currency = $priceService->getPriceById('EUR'); $query = new PriceQuery( new \Ibexa\Contracts\ProductCatalog\Values\Price\Query\Criterion\Currency($currency) ); ``` # Price CustomerGroup Criterion The `CustomerGroup` Search Criterion searches for prices based on the customer group. ## Arguments - `customer_group` - a single object or an array or `CustomerGroupInterface` objects that represent the customer group (`Ibexa\Contracts\ProductCatalog\Values\CustomerGroupInterface`) ## Example ### PHP ``` $customerGroup = $customerGroupService->getCustomerGroup(123); $query = new PriceQuery( new \Ibexa\Contracts\ProductCatalog\Values\Price\Query\Criterion\CustomerGroup($customerGroup) ); ``` # Price IsBasePrice Criterion The `IsBasePrice` Search Criterion searches for prices that are base prices. ## Arguments This Criterion takes no arguments. ## Limitations The `IsBasePrice` Criterion isn't available in Solr or Elasticsearch engines. ## Example ### PHP ``` $query = new PriceQuery( new \Ibexa\Contracts\ProductCatalog\Values\Price\Query\Criterion\IsBasePrice() ); ``` # Price IsCustomPrice Criterion The `IsCustomPrice` Search Criterion searches for prices that are custom prices. ## Arguments This Criterion takes no arguments. ## Limitations The `IsCustomPrice` Criterion isn't available in Solr or Elasticsearch engines. ## Example ### PHP ``` $query = new PriceQuery( new \Ibexa\Contracts\ProductCatalog\Values\Price\Query\Criterion\IsCustomPrice() ); ``` # Price LogicalAnd Criterion Editions: Commerce The `LogicalAnd` Search Criterion matches prices if all provided Criteria match. ## Arguments - `criterion` - a set of Criteria combined by the logical operator ## Example ### PHP ``` $query->query = new \Ibexa\Contracts\ProductCatalog\Values\Price\Query\Criterion\LogicalAnd( [ new \Ibexa\Contracts\ProductCatalog\Values\Price\Query\Criterion\Currency('USD'), new \Ibexa\Contracts\ProductCatalog\Values\Price\Query\Criterion\IsCustomPrice() ] ); ``` # Price LogicalOr Criterion Editions: Commerce The `LogicalOr` Search Criterion matches prices if at least one of the provided Criteria matches. ## Arguments - `criterion` - a set of Criteria combined by the logical operator ## Example ### PHP ``` $query->query = new \Ibexa\Contracts\ProductCatalog\Values\Price\Query\Criterion\LogicalOr( [ new \Ibexa\Contracts\ProductCatalog\Values\Price\Query\Criterion\Currency('USD'), new \Ibexa\Contracts\ProductCatalog\Values\Price\Query\Criterion\Currency('EUR') ] ); ``` # Price Product Criterion The `Product` Search Criterion searches for prices based on product codes. ## Arguments - `product_code` - a string that represents a product code or an array of codes ## Example ### PHP ``` $query = new PriceQuery( new \Ibexa\Contracts\ProductCatalog\Values\Price\Query\Criterion\Product('ergo_desk') ); ``` # Shipment Search Criteria reference Editions: Commerce Shipment Search Criteria are only supported by [Shipment Search (`ShipmentService::findShipments`)](https://doc.ibexa.co/en/latest/commerce/shipping_management/shipment_api/#get-multiple-shipments). With these Criteria you can filter shipments by their shipment identifier, shipment creation date, shipment status, shipping method, and more. ## Shipment Search Criteria | Search Criterion | Search based on | | ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | | [CreatedAt](https://doc.ibexa.co/en/latest/search/criteria_reference/shipment_createdat_criterion/index.md) | Date and time when shipment was created | | [Currency](https://doc.ibexa.co/en/latest/search/criteria_reference/shipment_currency_criterion/index.md) | Currency code | | [Id](https://doc.ibexa.co/en/latest/search/criteria_reference/shipment_id_criterion/index.md) | Shipment ID | | [Identifier](https://doc.ibexa.co/en/latest/search/criteria_reference/shipment_identifier_criterion/index.md) | Shipment identifier | | [LogicalAnd](https://doc.ibexa.co/en/latest/search/criteria_reference/shipment_logicaland_criterion/index.md) | Logical AND criterion that matches if all the provided Criteria match | | [LogicalOr](https://doc.ibexa.co/en/latest/search/criteria_reference/shipment_logicalor_criterion/index.md) | Logical OR criterion that matches if at least one of the provided Criteria matches | | [Owner](https://doc.ibexa.co/en/latest/search/criteria_reference/shipment_owner_criterion/index.md) | Owner based on the user reference | | [ShippingMethod](https://doc.ibexa.co/en/latest/search/criteria_reference/shipment_shipping_method_criterion/index.md) | Shipping method applied to the shipment | | [Status](https://doc.ibexa.co/en/latest/search/criteria_reference/shipment_status_criterion/index.md) | Status of the shipment | | [UpdatedAt](https://doc.ibexa.co/en/latest/search/criteria_reference/shipment_updatedat_criterion/index.md) | Date and time when status of the shipment was updated | # Shipment CreatedAt Criterion Editions: Commerce The `CreatedAt` Search Criterion searches for shipments based on the date when they were created. ## Arguments - `createdAt` - date to be matched, provided as a `DateTimeInterface` object - `operator` - optional operator string (EQ, GT, GTE, LT, LTE) ## Example ### PHP ``` $criteria = new \Ibexa\Contracts\Shipping\Shipment\Query\Criterion\CreatedAt( new DateTime('2023-03-01 14:07:02'), 'GTE' ); $query = new ShipmentQuery($criteria); ``` # Shipment Currency Criterion Editions: Commerce The `Currency` Search Criterion searches for shipments based on the currency code. ## Arguments - `currency` - an array of string currency codes ## Example ### PHP ``` $query = new ShipmentQuery( new \Ibexa\Contracts\Shipping\Shipment\Query\Criterion\Currency('USD', 'CZK') ); ``` # Shipment Id Criterion Editions: Commerce The `Id` Search Criterion searches for shipments based on the shipment ID. ## Arguments - `id` - integer that represents the shipment ID ## Example ### PHP ``` $query = new ShipmentQuery( new \Ibexa\Contracts\Shipping\Shipment\Query\Criterion\Id(2) ); ``` # Shipment Identifier Criterion Editions: Commerce The `Identifier` Search Criterion searches for shipments based on the shipment identifier. ## Arguments - `identifier` - string that represents the shipment identifier ## Example ### PHP ``` $query = new ShipmentQuery( new \Ibexa\Contracts\Shipping\Shipment\Query\Criterion\Identifier('f1t7z-3rb3rt') ); ``` # Shipment LogicalAnd Criterion Editions: Commerce The `LogicalAnd` Search Criterion matches shipments if all provided Criteria match. ## Arguments - `criterion` - a set of Criteria combined by the logical operator ## Example ### PHP ``` $query->query = new \Ibexa\Contracts\Shipping\Shipment\Query\Criterion\LogicalAnd( [ new \Ibexa\Contracts\Shipping\Shipment\Query\Criterion\CreatedAt(new DateTime('2023-03-01')), new \Ibexa\Contracts\Shipping\Shipment\Query\Criterion\ShippingMethod($shippingMethod) ] ); ``` # Shipment LogicalOr Criterion Editions: Commerce The `LogicalOr` Search Criterion matches shipments if at least one of the provided Criteria matches. ## Arguments - `criterion` - a set of Criteria combined by the logical operator ## Example ### PHP ``` $query->query = new \Ibexa\Contracts\Shipping\Shipment\Query\Criterion\LogicalOr( [ new \Ibexa\Contracts\Shipping\Shipment\Query\Criterion\CreatedAt(new DateTime('2023-03-01')), new \Ibexa\Contracts\Shipping\Shipment\Query\Criterion\ShippingMethod($shippingMethod) ] ); ``` # Owner Criterion Editions: Commerce The `Owner` Criterion searches for shipments based on the user reference. ## Arguments - `UserReference` object - \\Ibexa\\Contracts\\Core\\Repository\\Values\\User\\UserReference(int $userId) ## Example ``` $query = new ShipmentQuery( new \Ibexa\Contracts\Shipping\Shipment\Query\Criterion\Owner( \Ibexa\Contracts\Core\Repository\Values\User\UserReference(14) ) ); ``` `Owner` Criterion accepts also multiple values: ``` $query = new ShipmentQuery( new \Ibexa\Contracts\Shipping\Shipment\Query\Criterion\Owner( [ \Ibexa\Contracts\Core\Repository\Values\User\UserReference(14), \Ibexa\Contracts\Core\Repository\Values\User\UserReference(123), ] ) ); ``` # Shipment ShippingMethod Criterion Editions: Commerce The `ShippingMethod` Search Criterion searches for shipments based on a shipping method applied to them. ## Arguments - `value` - one or an array of `ShippingMethodInterface` objects that indicate the shipping methods ## Example ### PHP ``` $query = new ShipmentQuery( new \Ibexa\Contracts\Shipping\Shipment\Query\Criterion\ShippingMethod($shippingMethod) ); ``` # Shipment Status Criterion Editions: Commerce The `Status` Search Criterion searches for shipments based on shipment status. ## Arguments - `status` - string that represents the status of the shipment, takes values defined in shipment processing workflow ## Example ### PHP ``` $query = new ShipmentQuery( new \Ibexa\Contracts\Shipping\Shipment\Query\Criterion\Status('pending') ); ``` # Shipment UpdatedAt Criterion Editions: Commerce The `UpdatedAt` Search Criterion searches for shipments based on the date when their status was updated. ## Arguments - `updatedAt` - date to be matched, provided as a `DateTimeInterface` object - `operator` - optional operator string (EQ, GT, GTE, LT, LTE) ## Example ### PHP ``` $criteria = new \Ibexa\Contracts\Shipping\Shipment\Query\Criterion\UpdatedAt( new DateTime('2023-03-01'), 'GTE' ); $query = new ShipmentQuery($criteria); ``` # Shopping list search criteria reference Editions: LTS Update, Commerce The criteria are in the [`Ibexa\Contracts\ShoppingList\ShoppingList\Query\Criterion` namespace](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/namespaces/ibexa-contracts-shoppinglist-value-query-criterion.html) and implement the [`Ibexa\Contracts\ShoppingList\Value\Query\CriterionInterface` interface](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-Value-Query-CriterionInterface.html). | Criterion | Description | | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | | [`CreatedAtCriterion`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-Value-Query-Criterion-CreatedAtCriterion.html) | Find shopping lists created before or after a given date. | | [`IsDefaultCriterion`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-Value-Query-Criterion-IsDefaultCriterion.html) | Find shopping lists that are (or are not) the default one. | | [`LogicalAnd`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-Value-Query-Criterion-LogicalAnd.html) | Combine the criteria passed as arguments. | | [`NameCriterion`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-Value-Query-Criterion-NameCriterion.html) | Find shopping lists with a name containing the given string. | | [`OwnerCriterion`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-Value-Query-Criterion-OwnerCriterion.html) | Find shopping lists belonging to the given user or one of the given users. | | [`ProductCodeCriterion`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-Value-Query-Criterion-ProductCodeCriterion.html) | Find shopping lists containing an entry with the given product code. | | [`UpdatedAtCriterion`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-Value-Query-Criterion-UpdatedAtCriterion.html) | Find shopping lists updated before or after a given date. | The following example query returns all shopping lists available to the current user. If the user’s permissions include the [`ShoppingListOwner` `self` limitation](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#shopping-list-limitation), the query returns only lists created by that user. Otherwise, it returns all shopping lists in the system. ``` $query = new ShoppingListQuery(); ``` The following example query returns current user's shopping lists, excluding the default one, and sorts them by name: ``` use Ibexa\Contracts\ShoppingList\Value\Query; use Ibexa\Contracts\ShoppingList\Value\ShoppingListQuery; /** @var \Ibexa\Contracts\Core\Repository\PermissionResolver $permissionResolver */ $query = new ShoppingListQuery( new Query\Criterion\LogicalAnd( new Query\Criterion\OwnerCriterion($permissionResolver->getCurrentUserReference()), new Query\Criterion\IsDefaultCriterion(false) ), [ new Query\SortClause\Name(), ] ); ``` For more information about shopping lists search, see [List and search shopping lists](https://doc.ibexa.co/en/latest/commerce/shopping_list/shopping_list_api/#list-and-search-shopping-lists). # URL Search Criteria reference URL Search Criteria are only supported by [URL Search (`URLService::findUrls`)](https://doc.ibexa.co/en/latest/content_management/url_management/url_api/index.md). | URL criteria | URL based on | | ------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------- | | [LogicalAnd](https://doc.ibexa.co/en/latest/search/url_search_reference/logicaland_url_criterion/index.md) | Implements a logical AND Criterion. It matches if ALL of the provided Criteria match. | | [LogicalNot](https://doc.ibexa.co/en/latest/search/url_search_reference/logicalnot_url_criterion/index.md) | Implements a logical NOT Criterion. It matches if the provided Criterion doesn't match. | | [LogicalOr](https://doc.ibexa.co/en/latest/search/url_search_reference/logicalor_url_criterion/index.md) | Implements a logical OR Criterion. It matches if at least one of the provided Criteria match. | | [MatchAll](https://doc.ibexa.co/en/latest/search/url_search_reference/matchall_url_criterion/index.md) | Returns all URL results. | | [MatchNone](https://doc.ibexa.co/en/latest/search/url_search_reference/matchnone_url_criterion/index.md) | Returns no URL results. | | [Pattern](https://doc.ibexa.co/en/latest/search/url_search_reference/pattern_url_criterion/index.md) | Matches URLs that contain a pattern. | | [SectionId](https://doc.ibexa.co/en/latest/search/url_search_reference/sectionid_url_criterion/index.md) | Matches URLs from content placed in the Section with the specified ID. | | [SectionIdentifier](https://doc.ibexa.co/en/latest/search/url_search_reference/sectionidentifier_url_criterion/index.md) | Matches URLs from content placed in Sections with the specified identifiers. | | [Validity](https://doc.ibexa.co/en/latest/search/url_search_reference/validity_url_criterion/index.md) | Matches URLs based on validity flag. | | [VisibleOnly](https://doc.ibexa.co/en/latest/search/url_search_reference/visibleonly_url_criterion/index.md) | Matches URLs from published content. | # MatchAll Criterion The [`MatchAll` URL Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-URL-Query-Criterion-MatchAll.html) is an auxiliary Criterion that returns all search results. It's used internally when no filter or query is provided on a Query object. The Criterion takes no arguments. # MatchNone Criterion The [`MatchNone` URL Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-URL-Query-Criterion-MatchNone.html) is an auxiliary Criterion that returns no search results. It's used internally when no filter or query is provided on a Query object. The Criterion takes no arguments. # Pattern Criterion The [`Pattern` URL Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-URL-Query-Criterion-SectionId.html) matches URLs that contain the provided pattern. ## Arguments - `pattern` - string representing the pattern that needs to be a part of the URL ## Example ``` $query->filter = new Criterion\Pattern('ibexa.co'); ``` # SectionId Criterion The [`SectionId` URL Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-URL-Query-Criterion-SectionId.html) matches URLs based on the ID of the related content Section. ## Arguments - `sectionIds` - array of ints representing the IDs of the related content Sections ## Example ``` $query->filter = new Criterion\SectionId(['1', '3']); ``` # SectionIdentifier Criterion The [SectionIdentifier URL Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-URL-Query-Criterion-SectionIdentifier.html) matches URLs related to the content placed in a specified section identifier. ## Arguments - `sectionIdentifiers` - string(s) representing the identifiers of the Section(s) ## Example ``` $query->filter = new Criterion\SectionIdentifier(['standard', 'media']); ``` # Validity Criterion The [Validity URL Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-URL-Query-Criterion-Validity.html) matches URLs based on a validity flag. ## Arguments - `isValid` - bool representing whether the matcher selects only valid URLs ## Example ``` $query->filter = new Criterion\Validity(true); ``` # VisibleOnly Criterion The [`VisibleOnly` URL Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-URL-Query-Criterion-VisibleOnly.html) matches URLs from the published content. The Criterion takes no arguments. # LogicalAnd Criterion The [`LogicalAnd` URL Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-URL-Query-Criterion-LogicalAnd.html) matches a URL if all provided Criteria match. ## Arguments - `criterion` - the set of Criteria combined by the logical operator ## Example ``` $query->filter = new Criterion\LogicalAnd( [ new Criterion\Validity(true), new Criterion\Pattern('ibexa.co') ] ); ``` # LogicalNot Criterion The [`LogicalNot` URL Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-URL-Query-Criterion-LogicalNot.html) matches a URL if the provided Criterion doesn't match. It takes only one Criterion in the array parameter. ## Arguments - `criterion` - represents the Criterion that should be negated ## Example ``` $query->filter = new Criterion\LogicalNot( new Criterion\Pattern('ibexa.co') ); ``` # LogicalOr Criterion The [`LogicalOr` URL Criterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-URL-Query-Criterion-LogicalOr.html) matches a URL if at least one of the provided Criteria match. ## Arguments - `criterion` - the set of Criteria combined by the logical operator ## Example ``` $query->filter = new Criterion\LogicalOr( [ new Criterion\SectionIdentifier(['sports', 'news']), new Criterion\Pattern('ibexa.co') ] ); ``` # Activity Log Search Criteria reference Activity Log Search Criteria are found in the `Ibexa\Contracts\ActivityLog\Values\ActivityLog\Criterion` namespace. Those Criteria are to be used with `Ibexa\Contracts\ActivityLog\Values\ActivityLog\Query` for `Ibexa\Contracts\ActivityLog\ActivityLogServiceInterface::find`. They're applied to log entry groups. For example, with the criterion `ActionCriterion`, you get log entry groups that have at least one entry with this action (and possibly other actions as well). See [Searching in the Activity Log groups](https://doc.ibexa.co/en/latest/administration/recent_activity/recent_activity/#searching-in-the-activity-log-groups) for how to use a query, and an example combining several criteria. ## Value-based criteria | Search Criterion | Search based on | | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | | [`ActionCriterion`](https://doc.ibexa.co/en/latest/search/activity_log_search_reference/action_criterion/index.md) | Performed action name(s) | | [`LoggedAtCriterion`](https://doc.ibexa.co/en/latest/search/activity_log_search_reference/logged_at_criterion/index.md) | Before, after or at a given date and time | | [`ObjectCriterion`](https://doc.ibexa.co/en/latest/search/activity_log_search_reference/object_criterion/index.md) | Manipulated object's class name, and optionally objects' IDs | | [`ObjectNameCriterion`](https://doc.ibexa.co/en/latest/search/activity_log_search_reference/object_name_criterion/index.md) | Manipulated object's name, in whole or in part | | [`UserCriterion`](https://doc.ibexa.co/en/latest/search/activity_log_search_reference/user_criterion/index.md) | User performing the action | ## Logical criteria | Search Criterion | Description | | ---------------- | ----------------------------------------------------------------------------------- | | `LogicalNot` | Logical NOT criterion that matches if the provided Criteria don't match. | | `LogicalAnd` | Logical AND criterion that matches if all the provided Criteria match. | | `LogicalOr` | Logical OR criterion that matches if at least one of the provided Criteria matches. | # Action Criterion The `ActionCriterion` Activity Log Criterion matches activity log group that has a log entry with one of the given actions. ## Argument - `actions` - list of action name strings. A set of built-in names is available as `ActivityLogServiceInterface`'s `ACTION_` prefixed constants. ## Example ``` $query = new ActivityLog\Query([ new ActivityLog\Criterion\ActionCriterion([ ActivityLog\ActivityLogServiceInterface::ACTION_DELETE, ActivityLog\ActivityLogServiceInterface::ACTION_TRASH, ]), ]); ``` # LoggedAt Criterion The `LoggedAtCriterion` Activity Log Criterion matches activity log group that has a log entry created before or after a given date time. ## Arguments - `dateTime` - a [`DateTimeInterface`](https://www.php.net/manual/en/class.datetimeinterface.php) object, like [`DateTime`](https://www.php.net/manual/en/class.datetime.php) - `comparison` - string that represents a comparison sign. Available signs can be found as constant in the `LoggedAtCriterion` class itself | Comparison | Value | Constant | | --------------------- | ----- | ------------------------ | | Equal | `=` | `LoggedAtCriterion::EQ` | | Not equal | `<>` | `LoggedAtCriterion::NEQ` | | Less than | `<` | `LoggedAtCriterion::LT` | | Less than or equal | `<=` | `LoggedAtCriterion::LTE` | | Greater than | `>` | `LoggedAtCriterion::GT` | | Greater than or equal | `>=` | `LoggedAtCriterion::GTE` | ## Example The following example is to match all activity log groups that aren't older than a day: ``` $query = new ActivityLog\Query([ new ActivityLog\Criterion\LoggedAtCriterion(new \DateTime('- 1 day'), ActivityLog\Criterion\LoggedAtCriterion::GTE), ]); ``` # Object Criterion The `ObjectCriterion` Activity Log Criterion matches log group with a log entry about the given class name, and eventually one of the given IDs. ## Arguments - `objectClass` - a class of the object concerned by the searched log entries - `ids` - an optional list of object IDs ## Examples ``` $query = new ActivityLog\Query([ new ActivityLog\Criterion\ObjectCriterion(Ibexa\Contracts\Core\Repository\Values\Content\Content::class), ]); ``` ``` $query = new ActivityLog\Query([ new ActivityLog\Criterion\ObjectCriterion(Ibexa\Contracts\ProductCatalog\Values\ProductVariantInterface::class, [123, 234, 345]), ]); ``` # Object Name Criterion The `ObjectNameCriterion` Activity Log Criterion matches log groups that have a log entry with an object having a given string as name, or part of their name. ## Arguments - `query` - string representing the object name - `operator` - constant representing how to compare log names with the query - `ObjectNameCriterion::OPERATOR_CONTAINS` - `ObjectNameCriterion::OPERATOR_STARTS_WITH` - `ObjectNameCriterion::OPERATOR_ENDS_WITH` - `ObjectNameCriterion::OPERATOR_EQUALS` ## Example ``` $query = new ActivityLog\Query([ new ActivityLog\Criterion\ObjectNameCriterion('Ibexa', ActivityLog\Criterion\ObjectNameCriterion::OPERATOR_CONTAINS), ]); ``` # User Criterion The `UserCriterion` Activity Log Criterion matches log groups that have an activity by one of the users given by their IDs. ## Argument - `ids` - list of user IDs ## Example ``` $query = new ActivityLog\Query([ new ActivityLog\Criterion\UserCriterion([10, 14]), ]); ``` # Action Configuration Search Criterion reference Search criteria are found in the `Ibexa\Contracts\ConnectorAi\ActionConfiguration\Query\Criterion` namespace, implementing the [CriterionInterface](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-Query-CriterionInterface.html) interface: | Criterion | Description | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Name](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-Query-Criterion-Name.html) | Find Action Configurations matching given name. Use [FieldValueCriterion's constants](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-CoreSearch-Values-Query-Criterion-FieldValueCriterion.html#constants) like `FieldValueCriterion::COMPARISON_CONTAINS` or `FieldValueCriterion::COMPARISON_STARTS_WITH` to specify the matching condition | | [Enabled](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-Query-Criterion-Enabled.html) | Find enabled or disabled Action Configurations | | [Identifier](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-Query-Criterion-Identifier.html) | Find Action Configuration having the exact given identifier | | [LogicalAnd](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-Query-Criterion-LogicalAnd.html) | Composite criterion to group multiple criteria using the AND condition | | [LogicalOr](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-Query-Criterion-LogicalOr.html) | Composite criterion to group multiple criteria using the OR condition | | [Type](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-Query-Criterion-Type.html) | Find Action Configuration having the exact given type | The following example shows how to use them to find specific Action Configurations: ``` findActionConfigurations($query); ``` The result set contains Action Configurations that are: - enabled, and - with an identifier equal to `casual` or with a name starting with `Casual`. # Discounts Search Criterion reference Editions: Commerce Search Criteria are found in the [`Ibexa\Contracts\Discounts\Value\Query\Criterion`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/namespaces/ibexa-contracts-discounts-value-query-criterion.html) namespace, implementing the [CriterionInterface](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-CriterionInterface.html) interface: | Criterion | Description | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [CreatedAtCriterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-CreatedAtCriterion.html) | Find discounts with given creation date | | [CreatorCriterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-CreatorCriterion.html) | Find discounts created by specific users | | [EndDateCriterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-EndDateCriterion.html) | Find discounts by their end date. For permanent discounts, the end date is set to `null` | | [IndexedAtCriterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-IndexedAtCriterion.html) | Find discounts based on the date and time when they were indexed | | [IdentifierCriterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-IdentifierCriterion.html) | Find discounts by their identifier | | [IsEnabledCriterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-IsEnabledCriterion.html) | Find discounts by their status | | [LogicalAnd](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-LogicalAnd.html) | Composite criterion to group multiple criteria using the AND condition | | [LogicalOr](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-LogicalOr.html) | Composite criterion to group multiple criteria using the OR condition | | [NameCriterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-NameCriterion.html) | Find discounts by their name | | [PriorityCriterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-PriorityCriterion.html) | Find discounts by their priority | | [StartDateCriterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-StartDateCriterion.html) | Find discounts with given start date | | [TypeCriterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-TypeCriterion.html) | Find cart or catalog discounts by using constants from the [DiscountType](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-DiscountType.html) class | | [UpdatedAtCriterion](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-UpdatedAtCriterion.html) | Find discounts based on the date and time when they were updated | You can use the [FieldValueCriterion's constants](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-CoreSearch-Values-Query-Criterion-FieldValueCriterion.html#constants) like `FieldValueCriterion::COMPARISON_CONTAINS` or `FieldValueCriterion::COMPARISON_STARTS_WITH` to specify the operator for the condition. Use the `limit` and `offset` properties of [DiscountQuery](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-DiscountQuery.html#constants) to limit the number of results and implement pagination. The following example shows how you can use the criteria to find all the currently active discounts: ``` findDiscounts($query); ``` The criteria limit the result set to discounts matching all of the conditions listed below: - discount must be enabled - discount start date is not after the current date - discount end date is not before the current date or is not specified # Collaboration Search Criterion reference Search Criteria are found in the `Ibexa\Contracts\Collaboration\Invitation\Query\Criterion` namespace. Use them to work with objects related to [Collaborative editing API](https://doc.ibexa.co/en/latest/content_management/collaborative_editing/collaborative_editing_api/index.md). ## Invitation Search Criteria Invitation Search Criteria are implementing the [CriterionInterface](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Invitation-Query-CriterionInterface.html) interface: | Criterion | Description | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | [CreatedAt](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Invitation-Query-Criterion-CreatedAt.html) | Find invitations based on the date they were created | | [Id](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Invitation-Query-Criterion-Id.html) | Find invitations with given invitation ID | | [LogicalAnd](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Invitation-Query-Criterion-LogicalAnd.html) | Composite criterion to group multiple criteria using the AND condition | | [LogicalOr](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Invitation-Query-Criterion-LogicalOr.html) | Composite criterion to group multiple criteria using the OR condition | | [ParticipantScope](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Invitation-Query-Criterion-ParticipantScope.html) | Find invitations based on participant's scope, see [`ContentSessionScope`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Share-Collaboration-ContentSessionScope.html) for content-sharing sessions | | [ParticipantType](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Invitation-Query-Criterion-ParticipantType.html) | Find invitations based on participant type, see [`ParticipantDiscriminator`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Participant-ParticipantDiscriminator.html) | | [Sender](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Invitation-Query-Criterion-Sender.html) | Find invitations by invitation sender | | [Session](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Invitation-Query-Criterion-Session.html) | Find invitations by collaboration session | | [Status](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Invitation-Query-Criterion-Status.html) | Find invitations with given status | | [UpdatedAt](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Invitation-Query-Criterion-UpdatedAt.html) | Find invitations based on the date they were updated | ## Session Search Criteria Session Search Criteria are implementing the [CriterionInterface](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Session-Query-CriterionInterface.html) interface: | Criterion | Description | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | | [CreatedAt](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Session-Query-Criterion-CreatedAt.html) | Find sessions based on the date they were created | | [Email](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Session-Query-Criterion-Email.html) | Find sessions based on external participant email | | [Id](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Session-Query-Criterion-Id.html) | Find sessions with the session ID | | [IsActive](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Session-Query-Criterion-IsActive.html) | Find sessions based on active status | | [LogicalAnd](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Session-Query-Criterion-LogicalAnd.html) | Composite criterion to group multiple criteria using the AND condition | | [LogicalOr](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Session-Query-Criterion-LogicalOr.html) | Composite criterion to group multiple criteria using the OR condition | | [Owner](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Session-Query-Criterion-Owner.html) | Find sessions by their owner | | [ParticipantToken](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Session-Query-Criterion-ParticipantToken.html) | Find sessions by participant token | | [Token](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Session-Query-Criterion-Token.html) | Find sessions with given token | | [Type](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Session-Query-Criterion-Type.html) | Find sessions by type | | [UpdatedAt](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Session-Query-Criterion-UpdatedAt.html) | Find sessions based on the date they were updated | | [UserId](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Session-Query-Criterion-UserId.html) | Find sessions with given user ID | ### Example The following example shows how you can use the criteria to find all the currently active sessions: ``` findSessions($query); ``` The criteria limit the result set to sessions matching all of the conditions listed below: - session has an active status - session has a `content` type - session creation date is within the last week # Notification Search Criteria reference Notification Search Criteria are only supported by Notification Search (`NotificationService::findNotifications`). With these Criteria you can filter notifications by their notification creation date, notification status, and notification type. ## Notification Search Criteria | Search Criterion | Search based on | | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | | [DateCreated](https://doc.ibexa.co/en/latest/search/criteria_reference/notification_datecreated_criterion/index.md) | Date and time when notification was created | | [Status](https://doc.ibexa.co/en/latest/search/criteria_reference/notification_status_criterion/index.md) | Status of the notification | | [Type](https://doc.ibexa.co/en/latest/search/criteria_reference/notification_type_criterion/index.md) | Type of the notification | # Notification DateCreated Criterion The `DateCreated` Search Criterion searches for notifications based on the date when they were created. ## Arguments - `created` - date to be matched, provided as a `DateTimeInterface` object - `operator` - optional operator string (GTE, LTE) ## Example ### PHP ``` getRepository(); $notificationService = $repository->getNotificationService(); $query = new NotificationQuery([], 0, 25); $query->addCriterion(new Type('Workflow:Review')); $query->addCriterion(new Status(['unread'])); $from = new \DateTimeImmutable('-7 days'); $to = new \DateTimeImmutable(); $query->addCriterion(new DateCreated($from, $to)); $notificationList = $notificationService->findNotifications($query); ``` # Notification Status Criterion The `Status` Search Criterion searches for notifications based on notification status. ## Arguments - `status` - Boolean value that represents the status of the notification ## Example ### PHP ``` getRepository(); $notificationService = $repository->getNotificationService(); $query = new NotificationQuery([], 0, 25); $query->addCriterion(new Type('Workflow:Review')); $query->addCriterion(new Status(['unread'])); $from = new \DateTimeImmutable('-7 days'); $to = new \DateTimeImmutable(); $query->addCriterion(new DateCreated($from, $to)); $notificationList = $notificationService->findNotifications($query); ``` # Type Criterion The `Type` Search Criterion searches for notifications by their types. ## Arguments - `type` - string that represents the type of the notification, takes values defined in notification workflow ## Example ### PHP ``` getRepository(); $notificationService = $repository->getNotificationService(); $query = new NotificationQuery([], 0, 25); $query->addCriterion(new Type('Workflow:Review')); $query->addCriterion(new Status(['unread'])); $from = new \DateTimeImmutable('-7 days'); $to = new \DateTimeImmutable(); $query->addCriterion(new DateCreated($from, $to)); $notificationList = $notificationService->findNotifications($query); ``` # Sort Clause reference Sort Clauses are the sorting options for Content and Location Search and [Repository filtering](https://doc.ibexa.co/en/latest/search/search_api/#repository-filtering). Capabilities of individual Sort Clauses can depend on the search engine. All Sort Clauses can take the following optional argument: - `sortDirection` - the direction of the sorting, either `Query::SORT_ASC` (default) or `Query::SORT_DESC` ## Sort Clauses | Sort Clause | Sorting based on | Content Search | Location Search | Filtering | Trash | | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | -------------- | --------------- | --------- | ----- | | [ContentId](https://doc.ibexa.co/en/latest/search/sort_clause_reference/contentid_sort_clause/index.md) | Content items' ID | Yes | Yes | Yes | | | [ContentName](https://doc.ibexa.co/en/latest/search/sort_clause_reference/contentname_sort_clause/index.md) | Content names | Yes | Yes | Yes | Yes | | [ContentTranslatedName](https://doc.ibexa.co/en/latest/search/sort_clause_reference/contenttranslatedname_sort_clause/index.md) | Translated content names | Yes | Yes | | | | [ContentTypeName](https://doc.ibexa.co/en/latest/search/sort_clause_reference/contenttypename_sort_clause/index.md) | Content items' content type name | | | | Yes | | [CustomField](https://doc.ibexa.co/en/latest/search/sort_clause_reference/customfield_sort_clause/index.md) | Raw search index fields | Yes | Yes | | | | [DateModified](https://doc.ibexa.co/en/latest/search/sort_clause_reference/datemodified_sort_clause/index.md) | The date when content was last modified | Yes | Yes | Yes | | | [DatePublished](https://doc.ibexa.co/en/latest/search/sort_clause_reference/datepublished_sort_clause/index.md) | The date when content was created | Yes | Yes | Yes | | | [DateTrashed](https://doc.ibexa.co/en/latest/search/sort_clause_reference/datetrashed_sort_clause/index.md) | The date when content was sent to trash | | | | Yes | | [Depth](https://doc.ibexa.co/en/latest/search/sort_clause_reference/depth_sort_clause/index.md) | Location depth in the content tree | | Yes | Yes | Yes | | [Field](https://doc.ibexa.co/en/latest/search/sort_clause_reference/field_sort_clause/index.md) | Content of one of content item's fields | Yes | Yes | | | | [Id](https://doc.ibexa.co/en/latest/search/sort_clause_reference/id_sort_clause/index.md) | Location ID | | Yes | Yes | | | [IsMainLocation](https://doc.ibexa.co/en/latest/search/sort_clause_reference/ismainlocation_sort_clause/index.md) | Whether a location is the main location of a content item | | Yes | | | | [MapLocationDistance](https://doc.ibexa.co/en/latest/search/sort_clause_reference/maplocationdistance_sort_clause/index.md) | Distance between the location contained in a MapLocation field and the provided coordinates | Yes | Yes | | | | [Path](https://doc.ibexa.co/en/latest/search/sort_clause_reference/path_sort_clause/index.md) | PathString of the Location | | Yes | Yes | Yes | | [Priority](https://doc.ibexa.co/en/latest/search/sort_clause_reference/priority_sort_clause/index.md) | Location priority | | Yes | Yes | Yes | | [Random](https://doc.ibexa.co/en/latest/search/sort_clause_reference/random_sort_clause/index.md) | Random seed | Yes | Yes | | | | [Score](https://doc.ibexa.co/en/latest/search/sort_clause_reference/score_sort_clause/index.md) | Score of the search result | Yes | Yes | | | | [SectionIdentifier](https://doc.ibexa.co/en/latest/search/sort_clause_reference/sectionidentifier_sort_clause/index.md) | ID of the Section content is assigned to | Yes | Yes | Yes | | | [SectionName](https://doc.ibexa.co/en/latest/search/sort_clause_reference/sectionname_sort_clause/index.md) | Name of the Section content is assigned to | Yes | Yes | Yes | Yes | | [UserLogin](https://doc.ibexa.co/en/latest/search/sort_clause_reference/userlogin_sort_clause/index.md) | Login of the content item's creator | | | | Yes | | [Visibility](https://doc.ibexa.co/en/latest/search/sort_clause_reference/visibility_sort_clause/index.md) | Whether the location is visible or not | | Yes | Yes | | # ContentId Sort Clause The [`ContentId` Sort Clause](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-SortClause-ContentId.html) sorts search results by the content items' IDs. ## Arguments - (optional) `sortDirection` - Query or LocationQuery constant, either `Query::SORT_ASC` or `Query::SORT_DESC` ## Example ``` $query = new LocationQuery(); $query->sortClauses = [new SortClause\ContentId()]; ``` # ContentName Sort Clause The [`ContentName` Sort Clause](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-SortClause-ContentName.html) sorts search results by the content items' names. ## Arguments - (optional) `sortDirection` - Query or LocationQuery constant, either `Query::SORT_ASC` or `Query::SORT_DESC` ## Example ``` $query = new LocationQuery(); $query->sortClauses = [new SortClause\ContentName()]; ``` # ContentTranslatedName Sort Clause The [`ContentTranslatedName` Sort Clause](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-SortClause-ContentTranslatedName.html) sorts search results by the content items' translated names. ## Arguments - (optional) `sortDirection` - Query or LocationQuery constant, either `Query::SORT_ASC` or `Query::SORT_DESC` ## Limitations The `ContentTranslatedName` Sort Clause isn't available in [Repository filtering](https://doc.ibexa.co/en/latest/search/search_api/#repository-filtering). ## Example ``` $query = new LocationQuery(); $query->sortClauses = [new SortClause\ContentTranslatedName()]; ``` # ContentTypeName Sort Clause The [`ContentTypeName` Sort Clause](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-SortClause-Trash-ContentTypeName.html) sorts the results of searching in Trash by the name of the content item's content type. ## Arguments - (optional) `sortDirection` - Query constant, either `Query::SORT_ASC` or `Query::SORT_DESC` ## Example ``` $query = new Query(); $query->sortClauses = [new SortClause\Trash\ContentTypeName()]; ``` # CustomField Sort Clause The [`CustomField` Sort Clause](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-SortClause-CustomField.html) sorts search results by raw search index fields. ## Arguments - `field` - string representing the search index field name - (optional) `sortDirection` - Query or LocationQuery constant, either `Query::SORT_ASC` or `Query::SORT_DESC` ## Limitations > **Caution: Caution** > > To keep your project search engine independent, don't use the `CustomField` Sort Clause in production code. Valid use cases are: testing, or temporary (one-off) tools. The `CustomField` Sort Clause isn't available in [Repository filtering](https://doc.ibexa.co/en/latest/search/search_api/#repository-filtering). ## Example ``` $query = new LocationQuery(); $query->sortClauses = [new SortClause\CustomField('my_custom_field_s')]; ``` # DateModified Sort Clause The [`DateModified` Sort Clause](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-SortClause-DateModified.html) sorts search results by the date and time of the last modification of a content item. ## Arguments - (optional) `sortDirection` - Query or LocationQuery constant, either `Query::SORT_ASC` or `Query::SORT_DESC` ## Example ``` $query = new LocationQuery(); $query->sortClauses = [new SortClause\DateModified()]; ``` # DatePublished Sort Clause The [`DatePublished` Sort Clause](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-SortClause-DatePublished.html) sorts search results by the date and time of the first publication of a content item. ## Arguments - (optional) `sortDirection` - Query or LocationQuery constant, either `Query::SORT_ASC` or `Query::SORT_DESC` ## Example ``` $query = new LocationQuery(); $query->sortClauses = [new SortClause\DatePublished()]; ``` # DateTrashed Sort Clause The [`DateTrashed` Sort Clause](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-SortClause-Trash-DateTrashed.html) sorts the results of searching in Trash by the date and time when the content item was sent to trash. ## Arguments - (optional) `sortDirection` - Query constant, either `Query::SORT_ASC` or `Query::SORT_DESC` ## Example ``` $query = new Query(); $query->sortClauses = [new SortClause\Trash\DateTrashed()]; ``` # Depth Sort Clause The [`Location\Depth` Sort Clause](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-SortClause-Location-Depth.html) sorts search results by the depth of the location in the content tree. ## Arguments - (optional) `sortDirection` - Query or LocationQuery constant, either `Query::SORT_ASC` or `Query::SORT_DESC` ## Example ``` $query = new LocationQuery(); $query->sortClauses = [new SortClause\Depth()]; ``` # Field Sort Clause The [`Field` Sort Clause](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-SortClause-Field.html) sorts search results by the value of one of the content items' fields. Search results of the provided content type are sorted in field value order. Results of the query that don't belong to the content type are ranked lower. ## Arguments - `typeIdentifier` - string representing the identifier of the content type to which the field belongs - `fieldIdentifier` - string representing the identifier of the field to sort by - (optional) `sortDirection` - Query or LocationQuery constant, either `Query::SORT_ASC` or `Query::SORT_DESC` ## Limitations The `Field` Sort Clause isn't available in [Repository filtering](https://doc.ibexa.co/en/latest/search/search_api/#repository-filtering). ## Example ``` $query = new LocationQuery(); $query->sortClauses = [new SortClause\Field('article', 'title')]; ``` # Id Sort Clause The [`Location\Id` Sort Clause](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-SortClause-Location-Id.html) sorts search results by the ID of the location. ## Arguments - (optional) `sortDirection` - Query or LocationQuery constant, either `Query::SORT_ASC` or `Query::SORT_DESC` ## Example ``` $query = new LocationQuery(); $query->sortClauses = [new SortClause\Location\Id()]; ``` # IsMainLocation Sort Clause The [`Location\IsMainLocation` Sort Clause](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-SortClause-Location-IsMainLocation.html) sorts search results by whether their location is the main location of the content item. Locations that aren't main locations are ranked as lower values (for example, with ascending order they're returned first). ## Arguments - (optional) `sortDirection` - Query or LocationQuery constant, either `Query::SORT_ASC` or `Query::SORT_DESC` ## Limitations The `Location\IsMainLocation` Sort Clause isn't available in [Repository filtering](https://doc.ibexa.co/en/latest/search/search_api/#repository-filtering). ## Example ``` $query = new LocationQuery(); $query->sortClauses = [new SortClause\Location\InMainLocation()]; ``` # MapLocationDistance Sort Clause The [`MapLocationDistance` Sort Clause](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-SortClause-MapLocationDistance.html) sorts search results by the distance of the indicated MapLocation field to the provided location. ## Arguments - `typeIdentifier` - string representing the identifier of the content type to which the MapLocation field belongs - `fieldIdentifier` - string representing the identifier of the MapLocation field to sort by - `latitude` - float representing the latitude of the location to calculate distance to - `longitude`- float representing the longitude of the location to calculate distance to - (optional) `sortDirection` - Query or LocationQuery constant, either `Query::SORT_ASC` or `Query::SORT_DESC` ## Limitations The `MapLocationDistance` Sort Clause isn't available in [Repository filtering](https://doc.ibexa.co/en/latest/search/search_api/#repository-filtering). ## Example ``` $query = new LocationQuery(); $query->sortClauses = [new SortClause\MapLocationDistance('place', 'location', 49.542889, 20.111349)]; ``` # Path Sort Clause The [`Location\Path` Sort Clause](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-SortClause-Location-Path.html) sorts search results by the pathString of the location. > **Note: Note** > > Solr search engine uses dictionary sorting with the `Location/Path` Sort Clause. ## Arguments - (optional) `sortDirection` - Query or LocationQuery constant, either `Query::SORT_ASC` or `Query::SORT_DESC` ## Example ``` $query = new LocationQuery(); $query->sortClauses = [new SortClause\Location\Path()]; ``` # Priority Sort Clause The [`Location\Priority` Sort Clause](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-SortClause-Location-Priority.html) sorts search results by the priority of the location. ## Arguments - (optional) `sortDirection` - Query or LocationQuery constant, either `Query::SORT_ASC` or `Query::SORT_DESC` ## Example ``` $query = new LocationQuery(); $query->sortClauses = [new SortClause\Location\Priority()]; ``` # Random Sort Clause The [`Random` Sort Clause](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-SortClause-Random.html) orders search results randomly. ## Arguments - (optional) `seed` - int representing the random seed - (optional) `sortDirection` - Query or LocationQuery constant, either `Query::SORT_ASC` or `Query::SORT_DESC` ## Limitations The `Random` Sort Clause isn't available in [Repository filtering](https://doc.ibexa.co/en/latest/search/search_api/#repository-filtering). In Elasticsearch engine, you cannot combine the `Random` Sort Clause with any other Sort Clause. ## Example ``` $query = new LocationQuery(); $query->sortClauses = [new SortClause\Random()]; ``` # Score Sort Clause The [`Score` Sort Clause](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-SortClause-Score.html) orders search results by their score. ## Arguments - (optional) `sortDirection` - Query or LocationQuery constant, either `Query::SORT_ASC` or `Query::SORT_DESC` ## Limitations The `Score` Sort Clause isn't available in [Repository filtering](https://doc.ibexa.co/en/latest/search/search_api/#repository-filtering). ## Example ``` $query = new LocationQuery(); $query->sortClauses = [new SortClause\Score()]; ``` # SectionIdentifier Sort Clause The [`SectionIdentifier` Sort Clause](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-SortClause-SectionIdentifier.html) sorts search results by the Section IDs of the content items. ## Arguments - (optional) `sortDirection` - Query or LocationQuery constant, either `Query::SORT_ASC` or `Query::SORT_DESC` > **Note: Note** > > Solr search engine uses the `Query::SORT_DESC` sort direction by default. ## Example ``` $query = new LocationQuery(); $query->sortClauses = [new SortClause\SectionIdentifier()]; ``` # SectionName Sort Clause The [`SectionName` Sort Clause](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-SortClause-SectionName.html) sorts search results by the Section name of the content items. ## Arguments - (optional) `sortDirection` - Query or LocationQuery constant, either `Query::SORT_ASC` or `Query::SORT_DESC` ## Example ``` $query = new LocationQuery(); $query->sortClauses = [new SortClause\SectionName()]; ``` # UserLogin Sort Clause The [`UserLogin` Sort Clause](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-SortClause-Trash-UserLogin.html) sorts the results of searching in Trash by the login of the content item's creator. ## Arguments - (optional) `sortDirection` - Query constant, either `Query::SORT_ASC` or `Query::SORT_DESC` ## Example ``` $query = new Query(); $query->sortClauses = [new SortClause\Trash\UserLogin()]; ``` # Visibility Sort Clause The [`Location\Visibility` Sort Clause](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-SortClause-Location-Visibility.html) sorts search results by whether the location is visible or not. Locations that aren't visible are ranked as higher values (for example, with ascending order they're returned last). ## Arguments - (optional) `sortDirection` - Query or LocationQuery constant, either `Query::SORT_ASC` or `Query::SORT_DESC` ## Example ``` $query = new LocationQuery(); $query->sortClauses = [new SortClause\Location\Visibility()]; ``` # Content Type Search Sort Clauses Content Type Search Sort Clauses are the sorting options for content types. They're only supported by [Content Type Search (`ContentTypeService::findContentTypes`)](https://doc.ibexa.co/en/latest/content_management/content_api/managing_content/#finding-and-filtering-content-types). Sort Clauses are found in the [`Ibexa\Contracts\Core\Repository\Values\ContentType\Query\SortClause`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/namespaces/ibexa-contracts-core-repository-values-contenttype-query-sortclause.html) namespace: | Name | Description | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | | [Id](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-ContentType-Query-SortClause-Id.html) | Sort by content type's id | | [Identifier](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-ContentType-Query-SortClause-Identifier.html) | Sort by content type's identifier | | [Name](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-ContentType-Query-SortClause-Name.html) | Sort by content type's name | The following example shows how to use them to sort the searched content types: ``` contentTypeService->findContentTypes($query); $output->writeln('Found ' . $searchResult->getTotalCount() . ' content type(s):'); foreach ($searchResult->getContentTypes() as $contentType) { $output->writeln(sprintf( '- [%d] %s (identifier: %s)', $contentType->id, $contentType->getName(), $contentType->identifier )); } return Command::SUCCESS; } } ``` You can change the default sorting order by using the `SORT_ASC` and `SORT_DESC` constants from [`AbstractSortClause`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-CoreSearch-Values-Query-AbstractSortClause.html#constants). # Product Sort Clauses Product Sort Clauses are only supported by [Product Search (`ProductServiceInterface::findProduct`)](https://doc.ibexa.co/en/latest/product_catalog/product_api/#products). By using Sort Clause you can filter product by specific attributes, for example: price, code, or availability. To sort products coming from Quable PIM, see [Quable Search API](https://doc.ibexa.co/en/latest/product_catalog/quable/quable_api/#search-for-products) for details about the add-on. | Sort Clause | Sorting based on | Local product catalog | Quable PIM | | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | --------------------- | ---------- | | [BasePrice](https://doc.ibexa.co/en/latest/search/sort_clause_reference/baseprice_sort_clause/index.md) | Base product price | Yes | | | [CreatedAt](https://doc.ibexa.co/en/latest/search/sort_clause_reference/createdat_sort_clause/index.md) | Date and time of the creation of a product | Yes | Yes | | [CustomPrice](https://doc.ibexa.co/en/latest/search/sort_clause_reference/customprice_sort_clause/index.md) | Custom product price | Yes | | | [ProductAvailability](https://doc.ibexa.co/en/latest/search/sort_clause_reference/productavailability_sort_clause/index.md) | Product's availability | Yes | | | [ProductCode](https://doc.ibexa.co/en/latest/search/sort_clause_reference/productcode_sort_clause/index.md) | Product's code | Yes | Yes | | [ProductName](https://doc.ibexa.co/en/latest/search/sort_clause_reference/productname_sort_clause/index.md) | Product's name | Yes | Yes | # BasePrice Sort Clause The `BasePrice` Sort Clause sorts search results by the product's base price. ## Arguments - `currency` - a `CurrencyInterface` object representing the currency to check price for - (optional) `sortDirection` - ProductQuery constant, either `ProductQuery::SORT_ASC` or `ProductQuery::SORT_DESC` ## Limitations The `BasePrice` Sort Clause isn't available in the Legacy Search engine. ## Example ``` $sortClauses = [ new \Ibexa\Contracts\ProductCatalog\Values\Product\Query\SortClause\BasePrice( $currency, ProductQuery::SORT_ASC ) ]; $productQuery = new ProductQuery(null, null, $sortClauses); ``` # CreatedAt Sort Clause The `CreatedAt` Sort Clause sorts search results by the date and time of the creation of a product. ## Arguments - (optional) `sortDirection` - `CreatedAt` constant, either `CreatedAt::SORT_ASC` or `CreatedAt::SORT_DESC` ## Example ``` $productQuery = new ProductQuery( null, null, [ new \Ibexa\Contracts\ProductCatalog\Values\Product\Query\SortClause\CreatedAt( \Ibexa\Contracts\ProductCatalog\Values\Product\Query\SortClause\CreatedAt::SORT_ASC) ] ); ``` # CustomPrice Sort Clause The `CustomPrice` Sort Clause sorts search results by the product's custom price for a selected customer group. ## Arguments - `currency` - a `CurrencyInterface` object representing the currency to check price for - (optional) `sortDirection` - Query or LocationQuery constant, either `Query::SORT_ASC` or `Query::SORT_DESC` - (optional) `customerGroup` - a `CustomerGroupInterface` object representing the customer group to check prices for. If you don't provide a customer group, the query uses the group related to the current user. ## Limitations The `CustomPrice` Sort Clause isn't available in the Legacy Search engine. ## Example ``` $sortClauses = [ new \Ibexa\Contracts\ProductCatalog\Values\Product\Query\SortClause\CustomPrice( $currency, ProductQuery::SORT_ASC, $customerGroup ) ]; $productQuery = new ProductQuery(null, null, $sortClauses); ``` # ProductAvailability Sort Clause The `ProductAvailability` Sort Clause sorts search results by whether they have availability or not. ## Arguments - (optional) `sortDirection` - Query or LocationQuery constant, either `Query::SORT_ASC` or `Query::SORT_DESC` ## Example ``` $query = new ProductQuery( null, null, [ new \Ibexa\Contracts\ProductCatalog\Values\Product\Query\SortClause\ProductAvailability() ] ); ``` # ProductCode Sort Clause The `ProductCode` Sort Clause sorts search results by the product code. ## Arguments - (optional) `sortDirection` - Query or LocationQuery constant, either `Query::SORT_ASC` or `Query::SORT_DESC` ## Example ``` $query = new ProductQuery( null, null, [ new \Ibexa\Contracts\ProductCatalog\Values\Product\Query\SortClause\ProductCode() ] ); ``` # ProductName Sort Clause The `ProductName` Sort Clause sorts search results by the Product code. ## Arguments - (optional) `sortDirection` - Query or LocationQuery constant, either `Query::SORT_ASC` or `Query::SORT_DESC` ## Example ``` $query = new ProductQuery( null, null, [ new \Ibexa\Contracts\ProductCatalog\Values\Product\Query\SortClause\ProductName() ] ); ``` # Order Sort Clauses Editions: Commerce Order Sort Clauses are only supported by [Order Search (`OrderService::findOrders`)](https://doc.ibexa.co/en/latest/commerce/order_management/order_management_api/#get-multiple-orders). By using Sort Clauses you can sort orders by specific attributes, for example: creation date, status, and more. | Sort Clause | Sorting based on | | --------------------------------------------------------------------------------------------------------- | ------------------------------------------- | | [Id](https://doc.ibexa.co/en/latest/search/sort_clause_reference/order_id_sort_clause/index.md) | Order ID | | [Created](https://doc.ibexa.co/en/latest/search/sort_clause_reference/order_created_sort_clause/index.md) | Date and time when order was created | | [Updated](https://doc.ibexa.co/en/latest/search/sort_clause_reference/order_updated_sort_clause/index.md) | Date and time when order status was updated | | [Status](https://doc.ibexa.co/en/latest/search/sort_clause_reference/order_status_sort_clause/index.md) | Order status | # Order Id Sort Clause Editions: Commerce The `Id` Sort Clause sorts search results by order Id. ## Arguments - (optional) `sortDirection` - `Id` constant, either `Id::SORT_ASC` or `Id::SORT_DESC` ## Example ``` $orderQuery = new OrderQuery( $criteria, [ new \Ibexa\Contracts\OrderManagement\Value\Order\Query\SortClause\Id( \Ibexa\Contracts\OrderManagement\Value\Order\Query\SortClause\Id::SORT_ASC) ] ); ``` # Order Created Sort Clause Editions: Commerce The `Created` Sort Clause sorts search results by the date and time when the order was created. ## Arguments - (optional) `sortDirection` - `Created` constant, either `Created::SORT_ASC` or `Created::SORT_DESC` ## Example ``` $orderQuery = new OrderQuery( $criteria, [ new \Ibexa\Contracts\OrderManagement\Value\Order\Query\SortClause\Created( \Ibexa\Contracts\OrderManagement\Value\Order\Query\SortClause\Created::SORT_ASC) ] ); ``` # Order Updated Sort Clause Editions: Commerce The `Updated` Sort Clause sorts search results by the date and time when order status was updated. ## Arguments - (optional) `sortDirection` - `Updated` constant, either `Updated::SORT_ASC` or `Updated::SORT_DESC` ## Example ``` $orderQuery = new OrderQuery( $criteria, [ new \Ibexa\Contracts\OrderManagement\Value\Order\Query\SortClause\Updated( \Ibexa\Contracts\OrderManagement\Value\Order\Query\SortClause\Updated::SORT_ASC) ] ); ``` # Order Status Sort Clause Editions: Commerce The `Status` Sort Clause sorts search results by order status. ## Arguments - (optional) `sortDirection` - `Status` constant, either `Status::SORT_ASC` or `Status::SORT_DESC` ## Example ``` $orderQuery = new OrderQuery( $criteria, [ new \Ibexa\Contracts\OrderManagement\Value\Order\Query\SortClause\Status( \Ibexa\Contracts\OrderManagement\Value\Order\Query\SortClause\Status::SORT_ASC) ] ); ``` # Payment Sort Clauses Editions: Commerce Payment Sort Clauses are only supported by [Payment Search (`PaymentServiceInterface::findPayments`)](https://doc.ibexa.co/en/latest/commerce/payment/payment_api/#get-multiple-payments). By using Sort Clauses you can sort payments by specific attributes, for example: creation date, status, and more. | Sort Clause | Sorting based on | | ----------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | | [Id](https://doc.ibexa.co/en/latest/search/sort_clause_reference/payment_id_sort_clause/index.md) | Payment ID | | [Identifier](https://doc.ibexa.co/en/latest/search/sort_clause_reference/payment_identifier_sort_clause/index.md) | Payment identifier | | [CreatedAt](https://doc.ibexa.co/en/latest/search/sort_clause_reference/payment_createdat_sort_clause/index.md) | Date and time when payment was created | | [UpdatedAt](https://doc.ibexa.co/en/latest/search/sort_clause_reference/payment_updatedat_sort_clause/index.md) | Date and time when payment status was updated | | [Status](https://doc.ibexa.co/en/latest/search/sort_clause_reference/payment_status_sort_clause/index.md) | Payment status | # Payment Id Sort Clause Editions: Commerce The `Id` Sort Clause sorts search results by payment ID. ## Arguments - (optional) `sortDirection` - `Id` constant, either `Id::SORT_ASC` or `Id::SORT_DESC` ## Example ``` $paymentQuery = new PaymentQuery( $criteria, [ new \Ibexa\Contracts\Payment\Payment\Query\SortClause\Id( \Ibexa\Contracts\Payment\Payment\Query\SortClause\Id::SORT_ASC) ] ); ``` # Payment Identifier Sort Clause Editions: Commerce The `Identifier` Sort Clause sorts search results by payment identifier. ## Arguments - (optional) `sortDirection` - `Identifier` constant, either `Identifier::SORT_ASC` or `Identifier::SORT_DESC` ## Example ``` $paymentQuery = new PaymentQuery( $criteria, [ new \Ibexa\Contracts\Payment\Payment\Query\SortClause\Identifier( \Ibexa\Contracts\Payment\Payment\Query\SortClause\Identifier::SORT_ASC) ] ); ``` # Payment CreatedAt Sort Clause Editions: Commerce The `CreatedAt` Sort Clause sorts search results by the date and time when the payment was created. ## Arguments - (optional) `sortDirection` - `CreatedAt` constant, either `CreatedAt::SORT_ASC` or `CreatedAt::SORT_DESC` ## Example ``` $paymentQuery = new PaymentQuery( $criteria, [ new \Ibexa\Contracts\Payment\Payment\Query\SortClause\CreatedAt( \Ibexa\Contracts\Payment\Payment\Query\SortClause\CreatedAt::SORT_ASC) ] ); ``` # Payment UpdatedAt Sort Clause Editions: Commerce The `UpdatedAt` Sort Clause sorts search results by the date and time when payment status was updated. ## Arguments - (optional) `sortDirection` - `UpdatedAt` constant, either `UpdatedAt::SORT_ASC` or `UpdatedAt::SORT_DESC` ## Example ``` $paymentQuery = new PaymentQuery( $criteria, [ new \Ibexa\Contracts\Payment\Payment\Query\SortClause\UpdatedAt( \Ibexa\Contracts\Payment\Payment\Query\SortClause\UpdatedAt::SORT_ASC) ] ); ``` # Payment Status Sort Clause Editions: Commerce The `Status` Sort Clause sorts search results by payment status. ## Arguments - (optional) `sortDirection` - `Status` constant, either `Status::SORT_ASC` or `Status::SORT_DESC` ## Example ``` $paymentQuery = new PaymentQuery( $criteria, [ new \Ibexa\Contracts\Payment\Payment\Query\SortClause\Status( \Ibexa\Contracts\Payment\Payment\Query\SortClause\Status::SORT_ASC) ] ); ``` # Payment Method Sort Clauses Editions: Commerce Payment Method Sort Clauses are only supported by [Payment Method Search (`PaymentMethodService::findPaymentMethods`)](https://doc.ibexa.co/en/latest/commerce/payment/payment_method_api/#get-multiple-payment-methods). By using Sort Clauses you can sort payment methods by specific attributes, for example: creation date, ID, and more. | Sort Clause | Sorting based on | | ------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------- | | [CreatedAt](https://doc.ibexa.co/en/latest/search/sort_clause_reference/payment_method_createdat_sort_clause/index.md) | Date and time when payment method was created | | [Enabled](https://doc.ibexa.co/en/latest/search/sort_clause_reference/payment_method_enabled_sort_clause/index.md) | Payment method status | | [Id](https://doc.ibexa.co/en/latest/search/sort_clause_reference/payment_method_id_sort_clause/index.md) | Payment method ID | | [Identifier](https://doc.ibexa.co/en/latest/search/sort_clause_reference/payment_method_identifier_sort_clause/index.md) | Payment method identifier | | [UpdatedAt](https://doc.ibexa.co/en/latest/search/sort_clause_reference/payment_method_updatedat_sort_clause/index.md) | Date and time when payment method status was updated | # Payment Method CreatedAt Sort Clause Editions: Commerce The `CreatedAt` Sort Clause sorts search results by the date and time when the payment method was created. ## Arguments - (optional) `sortDirection` - `CreatedAt` constant, either `CreatedAt::SORT_ASC` or `CreatedAt::SORT_DESC` ## Example ``` $paymentMethodQuery = new PaymentMethodQuery( $criteria, [ new \Ibexa\Contracts\Payment\PaymentMethod\Query\SortClause\CreatedAt( \Ibexa\Contracts\Payment\PaymentMethod\Query\SortClause\CreatedAt::SORT_ASC) ] ); ``` # Payment Method Enabled Sort Clause Editions: Commerce The `Enabled` Sort Clause sorts search results by payment method status. ## Arguments - (optional) `sortDirection` - `Enabled` constant, either `Enabled::SORT_ASC` or `Enabled::SORT_DESC` ## Example ``` $paymentMethodQuery = new PaymentMethodQuery( $criteria, [ new \Ibexa\Contracts\Payment\PaymentMethod\Query\SortClause\Enabled( \Ibexa\Contracts\Payment\PaymentMethod\Query\SortClause\Enabled::SORT_DESC) ] ); ``` # Payment Method Id Sort Clause Editions: Commerce The `Id` Sort Clause sorts search results by payment method ID. ## Arguments - (optional) `sortDirection` - `Id` constant, either `Id::SORT_ASC` or `Id::SORT_DESC` ## Example ``` $paymentMethodQuery = new PaymentMethodQuery( $criteria, [ new \Ibexa\Contracts\Payment\PaymentMethod\Query\SortClause\Id( \Ibexa\Contracts\Payment\PaymentMethod\Query\SortClause\Id::SORT_ASC) ] ); ``` # Payment Method Identifier Sort Clause Editions: Commerce The `Identifier` Sort Clause sorts search results by payment method identifier. ## Arguments - (optional) `sortDirection` - `Identifier` constant, either `Identifier::SORT_ASC` or `Identifier::SORT_DESC` ## Example ``` $paymentMethodQuery = new PaymentMethodQuery( $criteria, [ new \Ibexa\Contracts\Payment\PaymentMethod\Query\SortClause\Identifier( \Ibexa\Contracts\Payment\PaymentMethod\Query\SortClause\Identifier::SORT_ASC) ] ); ``` # Payment Method UpdatedAt Sort Clause Editions: Commerce The `UpdatedAt` Sort Clause sorts search results by the date and time when payment method status was updated. ## Arguments - (optional) `sortDirection` - `UpdatedAt` constant, either `UpdatedAt::SORT_ASC` or `UpdatedAt::SORT_DESC` ## Example ``` $paymentMethodQuery = new PaymentMethodQuery( $criteria, [ new \Ibexa\Contracts\Payment\PaymentMethod\Query\SortClause\UpdatedAt( \Ibexa\Contracts\Payment\PaymentMethod\Query\SortClause\UpdatedAt::SORT_DESC) ] ); ``` # Shipment Sort Clauses Editions: Commerce Shipment Sort Clauses are only supported by [Shipment Search (`ShipmentService::findShipments`)](https://doc.ibexa.co/en/latest/commerce/shipping_management/shipment_api/#get-multiple-shipments). By using Sort Clauses you can sort shipments by specific attributes, for example, creation date or status. | Sort Clause | Sorting based on | | ------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------- | | [Id](https://doc.ibexa.co/en/latest/search/sort_clause_reference/shipment_id_sort_clause/index.md) | Shipment ID | | [Identifier](https://doc.ibexa.co/en/latest/search/sort_clause_reference/shipment_identifier_sort_clause/index.md) | Shipment identifier | | [CreatedAt](https://doc.ibexa.co/en/latest/search/sort_clause_reference/shipment_createdat_sort_clause/index.md) | Date and time when shipment was created | | [UpdatedAt](https://doc.ibexa.co/en/latest/search/sort_clause_reference/shipment_updatedat_sort_clause/index.md) | Date and time when shipment status was updated | | [Status](https://doc.ibexa.co/en/latest/search/sort_clause_reference/shipment_status_sort_clause/index.md) | Shipment status | # Shipment Id Sort Clause Editions: Commerce The `Id` Sort Clause sorts search results by shipment Id. ## Arguments - (optional) `sortDirection` - `Id` constant, either `Id::SORT_ASC` or `Id::SORT_DESC` ## Example ``` $shipmentQuery = new ShipmentQuery( $criteria, [ new \Ibexa\Contracts\Shipping\Shipment\Query\SortClause\Id( \Ibexa\Contracts\Shipping\Shipment\Query\SortClause\Id::SORT_ASC) ] ); ``` # Shipment Identifier Sort Clause Editions: Commerce The `Identifier` Sort Clause sorts search results by shipment identifier. ## Arguments - (optional) `sortDirection` - `Identifier` constant, either `Identifier::SORT_ASC` or `Identifier::SORT_DESC` ## Example ``` $shipmentQuery = new ShipmentQuery( $criteria, [ new \Ibexa\Contracts\Shipping\Shipment\Query\SortClause\Identifier( \Ibexa\Contracts\Shipping\Shipment\Query\SortClause\Identifier::SORT_ASC) ] ); ``` # Shipment CreatedAt Sort Clause Editions: Commerce The `CreatedAt` Sort Clause sorts search results by the date and time when the shipment was created. ## Arguments - (optional) `sortDirection` - `CreatedAt` constant, either `CreatedAt::SORT_ASC` or `CreatedAt::SORT_DESC` ## Example ``` $shipmentQuery = new ShipmentQuery( $criteria, [ new \Ibexa\Contracts\Shipping\Shipment\Query\SortClause\CreatedAt( \Ibexa\Contracts\Shipping\Shipment\Query\SortClause\CreatedAt::SORT_ASC) ] ); ``` # Shipment UpdatedAt Sort Clause Editions: Commerce The `UpdatedAt` Sort Clause sorts search results by the date and time when shipment status was updated. ## Arguments - (optional) `sortDirection` - `UpdatedAt` constant, either `UpdatedAt::SORT_ASC` or `UpdatedAt::SORT_DESC` ## Example ``` $shipmentQuery = new ShipmentQuery( $criteria, [ new \Ibexa\Contracts\Shipping\Shipment\Query\SortClause\UpdatedAt( \Ibexa\Contracts\Shipping\Shipment\Query\SortClause\UpdatedAt::SORT_ASC) ] ); ``` # Shipment Status Sort Clause Editions: Commerce The `Status` Sort Clause sorts search results by shipment status. ## Arguments - (optional) `sortDirection` - `Status` constant, either `Status::SORT_ASC` or `Status::SORT_DESC` ## Example ``` $shipmentQuery = new ShipmentQuery( $criteria, [ new \Ibexa\Contracts\Shipping\Shipment\Query\SortClause\Status( \Ibexa\Contracts\Shipping\Shipment\Query\SortClause\Status::SORT_ASC) ] ); ``` # Shopping list search sort clauses reference Editions: LTS Update, Commerce The sort clauses are in the [`Ibexa\Contracts\ShoppingList\Value\Query\SortClause` namespace](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/namespaces/ibexa-contracts-shoppinglist-value-query-sortclause.html). | Sort clause | Description | | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------ | | [`CreatedAt`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-Value-Query-SortClause-CreatedAt.html) | Sort by creation date | | [`IsDefault`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-Value-Query-SortClause-IsDefault.html) | Sort by being default or not | | [`Name`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-Value-Query-SortClause-Name.html) | Sort by name | | [`UpdatedAt`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ShoppingList-Value-Query-SortClause-UpdatedAt.html) | Sort by last modification date | The following example returns all the shopping lists available to the current user. The returned shopping list are sorted with the default shopping list on top, followed by the rest sorted by their name. ``` use Ibexa\Contracts\ShoppingList\Value\Query\SortClause\IsDefault; use Ibexa\Contracts\ShoppingList\Value\Query\SortClause\Name; use Ibexa\Contracts\ShoppingList\Value\ShoppingListQuery; /** @var \Ibexa\Contracts\ShoppingList\ShoppingListServiceInterface $shoppingListService */ $lists = $shoppingListService->findShoppingLists( new ShoppingListQuery( null, [ new IsDefault(IsDefault::SORT_DESC), new Name(), ] ) ); ``` For more information about shopping lists search, see [List and search shopping lists](https://doc.ibexa.co/en/latest/commerce/shopping_list/shopping_list_api/#list-and-search-shopping-lists). # URL Sort Clauses URL Sort Clauses are the sorting options for URLs. They're only supported by [URL Search (`URLService::findUrls`)](https://doc.ibexa.co/en/latest/content_management/url_management/url_api/index.md). All URL Sort Clauses can take the following optional argument: - `sortDirection` - the direction of the sorting, either `\Ibexa\Contracts\Core\Repository\Values\URL\Query\SortClause::SORT_ASC` (default) or `\Ibexa\Contracts\Core\Repository\Values\URL\Query\SortClause::SORT_DESC` | Sort Clause | Sorting based on | | ---------------------------------------------------------------------------------------------- | ---------------- | | [Id](https://doc.ibexa.co/en/latest/search/url_search_reference/id_url_sort_clause/index.md) | URL ID | | [URL](https://doc.ibexa.co/en/latest/search/url_search_reference/url_url_sort_clause/index.md) | URL address | # Id Sort Clause The [`SortClause\Id` Sort Clause](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-URL-Query-SortClause-Id.html) sorts search results by the ID of the URL. ## Arguments - `sortDirection` (optional) - the direction of the sorting, either `\Ibexa\Contracts\Core\Repository\Values\URL\Query\SortClause::SORT_ASC` (default) or `\Ibexa\Contracts\Core\Repository\Values\URL\Query\SortClause::SORT_DESC` ## Example ``` use Ibexa\Contracts\Core\Repository\Values\URL\URLQuery; use Ibexa\Contracts\Core\Repository\Values\URL\Query\SortClause; // ... $query = new URLQuery(); $query->sortClauses = [new SortClause\Id()]; ``` # URL Sort Clause The [`SortClause\Url` Sort Clause](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-URL-Query-SortClause-URL.html) sorts search results by the URLs. ## Arguments - `sortDirection` - the direction of the sorting, either `\Ibexa\Contracts\Core\Repository\Values\URL\Query\SortClause::SORT_ASC` (default) or `\Ibexa\Contracts\Core\Repository\Values\URL\Query\SortClause::SORT_DESC` ## Example ``` use Ibexa\Contracts\Core\Repository\Values\URL\URLQuery; use Ibexa\Contracts\Core\Repository\Values\URL\Query\SortClause; // ... $query = new URLQuery(); $query->sortClauses = [new SortClause\URL()]; ``` # Activity Log Search Sort Clauses reference See [Searching in the Activity Log groups](https://doc.ibexa.co/en/latest/administration/recent_activity/recent_activity/#searching-in-the-activity-log-groups) for the whole API. Sort Clauses are found in the `Ibexa\Contracts\ActivityLog\Values\ActivityLog\SortClause` namespace. - `LoggedAtSortClause`: Sort Activity Log entries by their date and time, descending or ascending. # Collaboration Search Sort Clauses reference Sort Clauses are found in the [`Ibexa\Contracts\Collaboration\Value\Query\SortClause`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/namespaces/ibexa-contracts-collaboration-invitation-query-sortclause.html) namespace. Use them to work with objects related to [Collaborative editing API](https://doc.ibexa.co/en/latest/content_management/collaborative_editing/collaborative_editing_api/index.md). ## Invitation Search Sort Clauses Invitation Search Sort Clauses are implementing the [SortClauseInterface](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Invitation-Query-SortClauseInterface.html) interface: | Name | Description | | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | | [CreatedAt](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Invitation-Query-SortClause-CreatedAt.html) | Sort by invitation's creation date | | [Id](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Invitation-Query-SortClause-Id.html) | Sort by invitation's ID | | [Status](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Invitation-Query-SortClause-Status.html) | Sort by invitation's status | | [UpdatedAt](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Invitation-Query-SortClause-UpdatedAt.html) | Sort by the date and time when invitation was updated | ## Session Search Sort Clauses Session Search Sort Clauses are implementing the [SortClauseInterface](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Session-Query-SortClauseInterface.html) interface: | Name | Description | | ------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | | [CreatedAt](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Session-Query-SortClause-CreatedAt.html) | Sort by session's creation date | | [Id](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Session-Query-SortClause-Id.html) | Sort by session's ID | | [UpdatedAt](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Session-Query-SortClause-UpdatedAt.html) | Sort by the date and time when session was updated | ### Example The following example shows how to use them to sort the searched sessions: ``` findSessions($query); ``` The returned active sessions are sorted by creation date (descending). # Action Configuration Search Sort Clauses reference Sort Clauses are found in the `Ibexa\Contracts\ConnectorAi\ActionConfiguration\Query\SortClause` namespace, implementing the [SortClauseInterface](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-Query-SortClauseInterface.html) interface: - [Enabled](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-Query-SortClause-Enabled.html) - [Id](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-Query-SortClause-Id.html) - [Identifier](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-Query-SortClause-Identifier.html) The following example shows how to use them to sort the searched Action Configurations: ``` findActionConfigurations($query); ``` The search results are sorted by: - status, with enabled on top - identifier, in ascending order. # Discounts Search Sort Clauses reference Editions: Commerce Sort Clauses are found in the [`Ibexa\Contracts\Discounts\Value\Query\SortClause`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/namespaces/ibexa-contracts-discounts-value-query-sortclause.html) namespace, implementing the [SortClauseInterface](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-SortClauseInterface.html) interface: | Name | Description | | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | [CreatedAt](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-SortClause-CreatedAt.html) | Sort by discount's creation date | | [EndDate](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-SortClause-EndDate.html) | Sort by discount's end date | | [Id](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-SortClause-Id.html) | Sort by discount's database ID | | [Identifier](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-SortClause-Identifier.html) | Sort by discount identifier | | [OverridePrioritization](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-SortClause-OverridePrioritization.html) | Sort prioritizing discounts with discount code over automatic ones | | [Priority](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-SortClause-Priority.html) | Sort by discount priority | | [StartDate](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-SortClause-StartDate.html) | Sort by discount start date | | [Type](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-SortClause-Type.html) | Sort by the place where the discount activates: catalog or cart. When sorting with ascending order, cart discounts are returned first. | | [UpdatedAt](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-SortClause-UpdatedAt.html) | Sort by discount modification date | The following example shows how to use them to sort the searched discounts: ``` findDiscounts($query); ``` The returned active discounts are sorted by: - the place where they activate: catalog or cart, with `cart` discounts returned first - priority (descending) - creation date (descending) You can change the default sorting order by using the `SORT_ASC` and `SORT_DESC` constants from [`AbstractSortClause`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-CoreSearch-Values-Query-AbstractSortClause.html#constants). # Aggregation reference [Aggregation](https://doc.ibexa.co/en/latest/search/search_api/#aggregation) is used to group search results into categories. There are three types of aggregations: - Term aggregations group by value and count object in each group - Range aggregations count values in specified ranges - Stats aggregations compute stats over numeric fields: minimum, average and maximum value, count, and sum of values > **Tip: Tip** > > Aggregations aren't available in the Legacy Search engine. ## Content aggregations | Name | Type | Based on | | ---------------------------------------------------------------------------------------------------------------------------------------- | ----- | ------------------------------------- | | [ContentTypeTermAggregation](https://doc.ibexa.co/en/latest/search/aggregation_reference/contenttypeterm_aggregation/index.md) | Term | Content type | | [ContentTypeGroupTermAggregation](https://doc.ibexa.co/en/latest/search/aggregation_reference/contenttypegroupterm_aggregation/index.md) | Term | Content type group | | [DateMetadataRangeAggregation](https://doc.ibexa.co/en/latest/search/aggregation_reference/datemetadatarange_aggregation/index.md) | Range | Date metadata | | [LanguageTermAggregation](https://doc.ibexa.co/en/latest/search/aggregation_reference/languageterm_aggregation/index.md) | Term | Content language | | [LocationChildrenTermAggregation](https://doc.ibexa.co/en/latest/search/aggregation_reference/locationchildrenterm_aggregation/index.md) | Term | Children on a Location | | [ObjectStateTermAggregation](https://doc.ibexa.co/en/latest/search/aggregation_reference/objectstateterm_aggregation/index.md) | Term | Object state | | [RawRangeAggregation](https://doc.ibexa.co/en/latest/search/aggregation_reference/rawrange_aggregation/index.md) | Range | Search index field | | [RawStatsAggregation](https://doc.ibexa.co/en/latest/search/aggregation_reference/rawstats_aggregation/index.md) | Stats | Search index field | | [RawTermAggregation](https://doc.ibexa.co/en/latest/search/aggregation_reference/rawterm_aggregation/index.md) | Term | Search index field | | [SectionTermAggregation](https://doc.ibexa.co/en/latest/search/aggregation_reference/sectionterm_aggregation/index.md) | Term | Section | | [SubtreeTermAggregation](https://doc.ibexa.co/en/latest/search/aggregation_reference/subtreeterm_aggregation/index.md) | Term | Location subtree path | | [TaxonomyEntryIdAggregation](https://doc.ibexa.co/en/latest/search/aggregation_reference/taxonomyentryid_aggregation/index.md) | Term | Taxonomy entry | | [UserMetadataTermAggregation](https://doc.ibexa.co/en/latest/search/aggregation_reference/usermetadataterm_aggregation/index.md) | Term | Content owner/owner group or modifier | | [VisibilityTermAggregation](https://doc.ibexa.co/en/latest/search/aggregation_reference/visibilityterm_aggregation/index.md) | Term | Content/Location visibility | ## Field aggregations | Name | Type | Based on field | | -------------------------------------------------------------------------------------------------------------------------- | ----- | ------------------------------------------------------------------------------------------------------------------------ | | [AuthorTermAggregation](https://doc.ibexa.co/en/latest/search/aggregation_reference/authorterm_aggregation/index.md) | Term | [Author](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/authorfield/index.md) | | [CheckboxTermAggregation](https://doc.ibexa.co/en/latest/search/aggregation_reference/checkboxterm_aggregation/index.md) | Term | [Checkbox](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/checkboxfield/index.md) | | [CountryTermAggregation](https://doc.ibexa.co/en/latest/search/aggregation_reference/countryterm_aggregation/index.md) | Term | [Country](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/countryfield/index.md) | | [DateRangeAggregation](https://doc.ibexa.co/en/latest/search/aggregation_reference/daterange_aggregation/index.md) | Range | [Date](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/datefield/index.md) | | [DateTimeRangeAggregation](https://doc.ibexa.co/en/latest/search/aggregation_reference/datetimerange_aggregation/index.md) | Range | [DateTime](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/dateandtimefield/index.md) | | [FloatRangeAggregation](https://doc.ibexa.co/en/latest/search/aggregation_reference/floatrange_aggregation/index.md) | Range | [Float](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/floatfield/index.md) | | [FloatStatsAggregation](https://doc.ibexa.co/en/latest/search/aggregation_reference/floatstats_aggregation/index.md) | Stats | [Float](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/floatfield/index.md) | | [IntegerRangeAggregation](https://doc.ibexa.co/en/latest/search/aggregation_reference/integerrange_aggregation/index.md) | Range | [Integer](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/integerfield/index.md) | | [IntegerStatsAggregation](https://doc.ibexa.co/en/latest/search/aggregation_reference/integerstats_aggregation/index.md) | Stats | [Integer](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/integerfield/index.md) | | [KeywordTermAggregation](https://doc.ibexa.co/en/latest/search/aggregation_reference/keywordterm_aggregation/index.md) | Term | [Keyword](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/keywordfield/index.md) | | [SelectionTermAggregation](https://doc.ibexa.co/en/latest/search/aggregation_reference/selectionterm_aggregation/index.md) | Term | [Selection](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/selectionfield/index.md) | | [TimeRangeAggregation](https://doc.ibexa.co/en/latest/search/aggregation_reference/timerange_aggregation/index.md) | Range | [Time](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/timefield/index.md) | ## Product aggregations | Name | Type | Based on | | ----------------------------------------------------------------------------------------------------------------------------------- | ------------ | ------------------------ | | [Product attribute](https://doc.ibexa.co/en/latest/search/aggregation_reference/product_attribute_aggregations/index.md) | Term / Range | Product attribute values | | [BasePriceStats](https://doc.ibexa.co/en/latest/search/aggregation_reference/basepricestats_aggregation/index.md) | Stats | Product base price | | [CustomPriceStats](https://doc.ibexa.co/en/latest/search/aggregation_reference/custompricestats_aggregation/index.md) | Stats | Product custom price | | [ProductAvailabilityTerm](https://doc.ibexa.co/en/latest/search/aggregation_reference/productavailabilityterm_aggregation/index.md) | Term | Product availability | | [ProductStockRange](https://doc.ibexa.co/en/latest/search/aggregation_reference/productstockrange_aggregation/index.md) | Range | Product stock | | [ProductPriceRange](https://doc.ibexa.co/en/latest/search/aggregation_reference/productpricerange_aggregation/index.md) | Range | Product price | | [ProductTypeTerm](https://doc.ibexa.co/en/latest/search/aggregation_reference/producttypeterm_aggregation/index.md) | Term | Product type | | [TaxonomyEntryIdAggregation](https://doc.ibexa.co/en/latest/search/aggregation_reference/taxonomyentryid_aggregation/index.md) | Term | Product category | # ContentTypeTermAggregation The [ContentTypeTermAggregation](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Aggregation-ContentTypeTermAggregation.html) aggregates search results by the content item's content type. ## Arguments - `name` - name of the Aggregation object ## Example ``` $query = new Query(); $query->aggregations[] = new Aggregation\ContentTypeTermAggregation('content_type'); ``` ## Settings You can define additional limits to the results using the `setLimit()` and `setMinCount()` methods. The following example limits the number of terms returned to 5 and only considers terms that have 10 or more results: ``` $aggregation = new //... $aggregation->setLimit(5); $aggregation->setMinCount(10); ``` # ContentTypeGroupTermAggregation The [ContentTypeGroupTermAggregation](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Aggregation-ContentTypeGroupTermAggregation.html) aggregates search results by the content item's content type group. ## Arguments - `name` - name of the Aggregation object ## Example ``` $query = new Query(); $query->aggregations[] = new Aggregation\ContentTypeGroupTermAggregation('content_type_group'); ``` ## Settings You can define additional limits to the results using the `setLimit()` and `setMinCount()` methods. The following example limits the number of terms returned to 5 and only considers terms that have 10 or more results: ``` $aggregation = new //... $aggregation->setLimit(5); $aggregation->setMinCount(10); ``` # DateMetadataRangeAggregation The [DateMetadataRangeAggregation](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Aggregation-Field-CountryTermAggregation.html) aggregates search results by the value of the content items' date metadata. ## Arguments - `name` - name of the Aggregation object - `type` - string representing the type of the Aggregation (`MODIFIED` or `PUBLISHED`) - `ranges` - array of Range objects that define the borders of the specific range sets ## Example ``` $query = new Query(); $query->aggregations[] = new Aggregation\DateMetadataRangeAggregation('date_metadata', Aggregation\DateMetadataRangeAggregation::PUBLISHED, [ new Query\Aggregation\Range(null, new DateTime('2020-06-01')), new Query\Aggregation\Range(new DateTime('2020-06-01'), new DateTime('2020-12-31')), new Query\Aggregation\Range(new DateTime('2020-12-31'), null), ]); ``` # LanguageTermAggregation The [LanguageTermAggregation](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Aggregation-LanguageTermAggregation.html) aggregates search results by the content item's language. ## Arguments - `name` - name of the Aggregation object ## Example ``` $query = new Query(); $query->aggregations[] = new Aggregation\LanguageTermAggregation('language'); ``` ## Settings You can define additional limits to the results using the `setLimit()` and `setMinCount()` methods. The following example limits the number of terms returned to 5 and only considers terms that have 10 or more results: ``` $aggregation = new //... $aggregation->setLimit(5); $aggregation->setMinCount(10); ``` # LocationChildrenTermAggregation The [LocationChildrenTermAggregation](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Aggregation-Location-LocationChildrenTermAggregation.html) aggregates search results by the number of children of a location. ## Arguments - `name` - name of the Aggregation object ## Example ``` $query = new LocationQuery(); $query->aggregations[] = new Aggregation\Location\LocationChildrenTermAggregation('location_children'); ``` ## Settings You can define additional limits to the results using the `setLimit()` and `setMinCount()` methods. The following example limits the number of terms returned to 5 and only considers terms that have 10 or more results: ``` $aggregation = new //... $aggregation->setLimit(5); $aggregation->setMinCount(10); ``` # ObjectStateTermAggregation The [ObjectStateTermAggregation](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Aggregation-ObjectStateTermAggregation.html) aggregates search results by the content item's object state. ## Arguments - `name` - name of the Aggregation object - `objectStateGroupIdentifier` - string representing the identifier of the object state group to aggregate results by ## Example ``` $query = new Query(); $query->aggregations[] = new Aggregation\Location\ObjectStateTermAggregation('object_state', 'ibexa_lock'); ``` ## Settings You can define additional limits to the results using the `setLimit()` and `setMinCount()` methods. The following example limits the number of terms returned to 5 and only considers terms that have 10 or more results: ``` $aggregation = new //... $aggregation->setLimit(5); $aggregation->setMinCount(10); ``` # RawRangeAggregation The [RawRangeAggregation](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Aggregation-RawRangeAggregation.html) aggregates search results by the value of the selected search index field. ## Arguments - `name` - name of the Aggregation object - `field` - string representing the search index field - `ranges` - array of Range objects that define the borders of the specific range sets ## Limitations > **Caution: Caution** > > To keep your project search engine independent, don't use the `RawRangeAggregation` Aggregation in production code. Valid use cases are: testing, or temporary (one-off) tools. ## Example ``` $query = new LocationQuery(); $query->aggregations[] = new Aggregation\RawRangeAggregation('priority', 'priority_id', [ new Query\Aggregation\Range(1, 10), new Query\Aggregation\Range(10, 100) ]); ``` # RawStatsAggregation The [RawStatsAggregation](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Aggregation-RawStatsAggregation.html) aggregates search results by the value of the selected search index field and provides statistical information for the values. You can use the provided getters to access the values: - sum (`getSum()`) - count of values (`getCount()`) - minimum value (`getMin()`) - maximum value (`getMax()`) - average (`getAvg()`) ## Arguments - `name` - name of the Aggregation object - `field` - string representing the search index field ## Limitations > **Caution: Caution** > > To keep your project search engine independent, don't use the `RawStatsAggregation` Aggregation in production code. Valid use cases are: testing, or temporary (one-off) tools. ## Example ``` $query = new Query(); $query->aggregations[] = new Aggregation\RawStatsAggregation('location_depth', 'depth_i'); ``` # RawTermAggregation The [RawTermAggregation](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Aggregation-RawTermAggregation.html) aggregates search results by the value of the selected search index field. ## Arguments - `name` - name of the Aggregation object - `field` - string representing the search index field ## Limitations > **Caution: Caution** > > To keep your project search engine independent, don't use the `RawTermAggregation` Aggregation in production code. Valid use cases are: testing, or temporary (one-off) tools. ## Example ``` $query = new Query(); $query->aggregations[] = new Aggregation\RawTermAggregation('content_per_content_type', 'content_type_id_id'); ``` ## Settings You can define additional limits to the results using the `setLimit()` and `setMinCount()` methods. The following example limits the number of terms returned to 5 and only considers terms that have 10 or more results: ``` $aggregation = new //... $aggregation->setLimit(5); $aggregation->setMinCount(10); ``` # SectionTermAggregation The [SectionTermAggregation](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Aggregation-SectionTermAggregation.html) aggregates search results by the content item's section. ## Arguments - `name` - name of the Aggregation object ## Example ``` $query = new Query(); $query->aggregations[] = new Aggregation\SectionTermAggregation('section'); ``` ## Settings You can define additional limits to the results using the `setLimit()` and `setMinCount()` methods. The following example limits the number of terms returned to 5 and only considers terms that have 10 or more results: ``` $aggregation = new //... $aggregation->setLimit(5); $aggregation->setMinCount(10); ``` # SubtreeTermAggregation The [SubtreeTermAggregation](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Aggregation-Location-SubtreeTermAggregation.html) aggregates search results by the location's subtree path. ## Arguments - `name` - name of the Aggregation object - `pathString` - string representing the pathstring to aggregate results by ## Example ``` $query = new Query(); $query->aggregations[] = new Aggregation\Location\SubtreeTermAggregation('pathstring', '/1/2/'); ``` ## Settings You can define additional limits to the results using the `setLimit()` and `setMinCount()` methods. The following example limits the number of terms returned to 5 and only considers terms that have 10 or more results: ``` $aggregation = new //... $aggregation->setLimit(5); $aggregation->setMinCount(10); ``` # TaxonomyEntryIdAggregation The `TaxonomyEntryIdAggregation` aggregates search results by the content item's taxonomy entry or a product's category. ## Arguments - `name` - name of the Aggregation object - `taxonomyIdentifier` - identifier of the taxonomy to aggregate results by ## Example ``` $query = new Query(); $query->aggregations[] = new Aggregation\TaxonomyEntryIdAggregation('taxonomy', 'tags'); ``` ``` $query = new ProductQuery(); $query->aggregations[] = new Aggregation\TaxonomyEntryIdAggregation('categories', 'product_categories'); ``` # UserMetadataTermAggregation The [UserMetadataTermAggregation](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Aggregation-UserMetadataTermAggregation.html) aggregates search results by the User content item's metadata. ## Arguments - `name` - name of the Aggregation object ## Example ``` $query = new Query(); $query->aggregations[] = new Aggregation\UserMetadataTermAggregation('user_metadata'); ``` ## Settings You can define additional limits to the results using the `setLimit()` and `setMinCount()` methods. The following example limits the number of terms returned to 5 and only considers terms that have 10 or more results: ``` $aggregation = new //... $aggregation->setLimit(5); $aggregation->setMinCount(10); ``` # VisibilityTermAggregation The [VisibilityTermAggregation](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Aggregation-VisibilityTermAggregation.html) aggregates search results by the content item's visibility. ## Arguments - `name` - name of the Aggregation object ## Example ``` $query = new Query(); $query->aggregations[] = new Aggregation\VisibilityTermAggregation('visibility'); ``` ## Settings You can define additional limits to the results using the `setLimit()` and `setMinCount()` methods. The following example limits the number of terms returned to 5 and only considers terms that have 10 or more results: ``` $aggregation = new //... $aggregation->setLimit(5); $aggregation->setMinCount(10); ``` # AuthorTermAggregation The field-based [AuthorTermAggregation](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Aggregation-Field-AuthorTermAggregation.html) aggregates search results by the value of the Author field. ## Arguments - `name` - name of the Aggregation - `contentTypeIdentifier` - string representing the content type identifier - `fieldDefinitionIdentifier` - string representing the Field Definition identifier ## Example ``` $query = new Query(); $query->aggregations[] = new Aggregation\Field\AuthorTermAggregation('author', 'article', 'authors'); ``` ## Settings You can define additional limits to the results using the `setLimit()` and `setMinCount()` methods. The following example limits the number of terms returned to 5 and only considers terms that have 10 or more results: ``` $aggregation = new //... $aggregation->setLimit(5); $aggregation->setMinCount(10); ``` # CheckboxTermAggregation The field-based [CheckboxTermAggregation](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Aggregation-Field-CheckboxTermAggregation.html) aggregates search results by the value of the Checkbox field. ## Arguments - `name` - name of the Aggregation - `contentTypeIdentifier` - string representing the content type identifier - `fieldDefinitionIdentifier` - string representing the Field Definition identifier ## Example ``` $query = new Query(); $query->aggregations[] = new Aggregation\Field\CheckboxTermAggregation('checkbox', 'article', 'enable_comments'); ``` ## Settings You can define additional limits to the results using the `setLimit()` and `setMinCount()` methods. The following example limits the number of terms returned to 5 and only considers terms that have 10 or more results: ``` $aggregation = new //... $aggregation->setLimit(5); $aggregation->setMinCount(10); ``` # CountryTermAggregation The field-based [CountryTermAggregation](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Aggregation-Field-CountryTermAggregation.html) aggregates search results by the value of the Country field. ## Arguments - `name` - name of the Aggregation - `contentTypeIdentifier` - string representing the content type identifier - `fieldDefinitionIdentifier` - string representing the Field Definition identifier ## Example ``` $query = new Query(); $query->aggregations[] = new Aggregation\Field\CountryTermAggregation('country', 'article', 'country'); ``` ## Settings You can define additional limits to the results using the `setLimit()` and `setMinCount()` methods. The following example limits the number of terms returned to 5 and only considers terms that have 10 or more results: ``` $aggregation = new //... $aggregation->setLimit(5); $aggregation->setMinCount(10); ``` # DateRangeAggregation The field-based [DateRangeAggregation](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Aggregation-Field-DateRangeAggregation.html) aggregates search results by the value of the Date, DateTime, or Time field. ## Arguments - `name` - name of the Aggregation - `contentTypeIdentifier` - string representing the content type identifier - `fieldDefinitionIdentifier` - string representing the Field Definition identifier - `ranges` - array of Range objects that define the borders of the specific range sets ## Example ``` $query = new Query(); $query->aggregations[] = new Aggregation\Field\DateRangeAggregation('date', 'event', 'event_date', [ new Query\Aggregation\Range(null, new DateTime('2020-06-01')), new Query\Aggregation\Range(new DateTime('2020-06-01'), new DateTime('2020-12-31')), new Query\Aggregation\Range(new DateTime('2020-12-31'), null), ]); ``` # DateTimeRangeAggregation The field-based [DateTimeRangeAggregation](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Aggregation-Field-DateTimeRangeAggregation.html) aggregates search results by the value of the Date, DateTime, or Time field. ## Arguments - `name` - name of the Aggregation - `contentTypeIdentifier` - string representing the content type identifier - `fieldDefinitionIdentifier` - string representing the Field Definition identifier - `ranges` - array of Range objects that define the borders of the specific range sets ## Example ``` $query = new Query(); $query->aggregations[] = new Aggregation\Field\DateTimeRangeAggregation('date', 'event', 'event_date', [ new Query\Aggregation\Range(null, new DateTime('2020-06-01')), new Query\Aggregation\Range(new DateTime('2020-06-01'), new DateTime('2020-12-31')), new Query\Aggregation\Range(new DateTime('2020-12-31'), null), ]); ``` # FloatRangeAggregation The field-based [FloatRangeAggregation](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Aggregation-Field-FloatRangeAggregation.html) aggregates search results by the value of the Float field. ## Arguments - `name` - name of the Aggregation - `contentTypeIdentifier` - string representing the content type identifier - `fieldDefinitionIdentifier` - string representing the Field Definition identifier - `ranges` - array of Range objects that define the borders of the specific range sets ## Example ``` $query = new Query(); $query->aggregations[] = new Aggregation\Field\FloatRangeAggregation('float', 'product', 'weight', [ new Query\Aggregation\Range(null, 0.25), new Query\Aggregation\Range(0.25, 0.75), new Query\Aggregation\Range(0.75, null), ]); ``` # FloatStatsAggregation The field-based [FloatStatsAggregation](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Aggregation-Field-FloatStatsAggregation.html) aggregates search results by the value of the Float field and provides statistical information for the values. You can use the provided getters to access the values: - sum (`getSum()`) - count of values (`getCount()`) - minimum value (`getMin()`) - maximum value (`getMax()`) - average (`getAvg()`) ## Arguments - `name` - name of the Aggregation - `contentTypeIdentifier` - string representing the content type identifier - `fieldDefinitionIdentifier` - string representing the Field Definition identifier ## Example ``` $query = new Query(); $query->aggregations[] = new Aggregation\Field\FloatStatsAggregation('float', 'product', 'weight'); ``` # IntegerRangeAggregation The field-based [IntegerRangeAggregation](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Aggregation-Field-IntegerRangeAggregation.html) aggregates search results by the value of the Integer field. ## Arguments - `name` - name of the Aggregation - `contentTypeIdentifier` - string representing the content type identifier - `fieldDefinitionIdentifier` - string representing the Field Definition identifier - `ranges` - array of Range objects that define the borders of the specific range sets ## Example ``` $query = new Query(); $query->aggregations[] = new Aggregation\Field\IntegerRangeAggregation('integer', 'product', 'amount', [ new Query\Aggregation\Range(null, 12), new Query\Aggregation\Range(12, 24), new Query\Aggregation\Range(24, null), ]); ``` # IntegerStatsAggregation The field-based [IntegerStatsAggregation](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Aggregation-Field-IntegerStatsAggregation.html) aggregates search results by the value of the Integer field and provides statistical information for the values. You can use the provided getters to access the values: - sum (`getSum()`) - count of values (`getCount()`) - minimum value (`getMin()`) - maximum value (`getMax()`) - average (`getAvg()`) ## Arguments - `name` - name of the Aggregation - `contentTypeIdentifier` - string representing the content type identifier - `fieldDefinitionIdentifier` - string representing the Field Definition identifier ## Example ``` $query = new Query(); $query->aggregations[] = new Aggregation\Field\IntegerStatsAggregation('integer', 'product', 'amount'); ``` # KeywordTermAggregation The field-based [KeywordTermAggregation](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Aggregation-Field-KeywordTermAggregation.html) aggregates search results by the value of the Keyword field. ## Arguments - `name` - name of the Aggregation - `contentTypeIdentifier` - string representing the content type identifier - `fieldDefinitionIdentifier` - string representing the Field Definition identifier ## Example ``` $query = new Query(); $query->aggregations[] = new Aggregation\Field\KeywordTermAggregation('keyword', 'article', 'tags'); ``` ## Settings You can define additional limits to the results using the `setLimit()` and `setMinCount()` methods. The following example limits the number of terms returned to 5 and only considers terms that have 10 or more results: ``` $aggregation = new //... $aggregation->setLimit(5); $aggregation->setMinCount(10); ``` # SelectionTermAggregation The field-based [SelectionTermAggregation](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Aggregation-Field-SelectionTermAggregation.html) aggregates search results by the value of the Selection field. ## Arguments - `name` - name of the Aggregation - `contentTypeIdentifier` - string representing the content type identifier - `fieldDefinitionIdentifier` - string representing the Field Definition identifier ## Example ``` $query = new Query(); $query->aggregations[] = new Aggregation\Field\SelectionTermAggregation('selection', 'article', 'select'); ``` ## Settings You can define additional limits to the results using the `setLimit()` and `setMinCount()` methods. The following example limits the number of terms returned to 5 and only considers terms that have 10 or more results: ``` $aggregation = new //... $aggregation->setLimit(5); $aggregation->setMinCount(10); ``` # TimeRangeAggregation The field-based [TimeRangeAggregation](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Aggregation-Field-TimeRangeAggregation.html) aggregates search results by the value of the Date, DateTime, or Time field. ## Arguments - `name` - name of the Aggregation - `contentTypeIdentifier` - string representing the content type identifier - `fieldDefinitionIdentifier` - string representing the Field Definition identifier - `ranges` - array of Range objects that define the borders of the specific range sets ## Example ``` $query = new Query(); $query->aggregations[] = new Aggregation\Field\TimeRangeAggregation('date', 'event', 'event_time', [ new Query\Aggregation\Range(null, new DateTime('T14:00')), new Query\Aggregation\Range(new DateTime('T14:003'), null), ]); ``` # Product attribute aggregations Product attribute aggregations aggregate search results by the value of the product's attributes. Depending on attribute type, the following aggregations are available: - `ProductAttributeBooleanAggregation` - `ProductAttributeColorAggregation` - `ProductAttributeFloatAggregation` - `ProductAttributeFloatRangeAggregation` - `ProductAttributeIntegerAggregation` - `ProductAttributeIntegerRangeAggregation` - `ProductAttributeSelectionAggregation` ## Arguments - `name` - name of the Aggregation - `attributeDefinitionIdentifier` - identifier of the attribute Range aggregations (`ProductAttributeFloatRangeAggregation` and `ProductAttributeIntegerRangeAggregation`) additionally take: - `ranges` - array of Range objects that define the borders of the specific range sets ## Example ``` $query = new ProductQuery(); $query->setAggregations([ new ProductAttributeSelectionAggregation('skin', 'skin_type'), ]); ``` ``` $query = new ProductQuery(); $query->setAggregations([ new ProductAttributeIntegerRangeAggregation('buttons', 'number_of_buttons', [ new \Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\Range(null, 5), new \Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\Range(5, 10), new \Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\Range(10, null), ]), ]); ``` # BasePriceStatsAggregation The BasePriceStatsAggregation aggregates search results by the value of the product's price can provides statistical information for the values. You can use the provided getters to access the values: - sum (`getSum()`) - count of values (`getCount()`) - minimum value (`getMin()`) - maximum value (`getMax()`) - average (`getAvg()`) ## Arguments - `name` - name of the Aggregation - `\Ibexa\Contracts\ProductCatalog\Values\CurrencyInterface` - currency of the price ## Example ``` $query = new ProductQuery(); $query->setAggregations([ new BasePriceStatsAggregation('base_price_stats_aggregation', $currency), ]); ``` # CustomPriceStatsAggregation The CustomPriceStatsAggregation aggregates search results by the value of the custom product's price and provides statistical information for the values. You can use the provided getters to access the values: - sum (`getSum()`) - count of values (`getCount()`) - minimum value (`getMin()`) - maximum value (`getMax()`) - average (`getAvg()`) ## Arguments - `name` - name of the Aggregation - `\Ibexa\Contracts\ProductCatalog\Values\CurrencyInterface` - currency of the price - `\Ibexa\Contracts\ProductCatalog\Values\CustomerGroupInterface|null` - customer group that defines custom pricing, by default it's the one assigned to current user ## Example ``` $query = new ProductQuery(); $query->setAggregations([ new CustomPriceStatsAggregation('custom_price_stats_aggregation', $currency, $customerGroup), ]); ``` # ProductAvailabilityTerm The ProductAvailabilityTermAggregation aggregates search results by product availability (available/unavailable). ## Arguments - `name` - name of the Aggregation object ## Example ``` $query = new ProductQuery(); $query->setAggregations([ new ProductAvailabilityTermAggregation('product_availability'), ]); ``` # ProductStockRangeAggregation The ProductStockRangeAggregation aggregates search results by products' numerical stock. ## Arguments - `name` - name of the Aggregation - `ranges` - array of Range objects that define the borders of the specific range sets ## Example ``` $productQuery = new ProductQuery(); $productQuery->setAggregations([ new ProductStockRangeAggregation('stock', [ new Range(null, 10), new Range(10, 100), new Range(100, null), ]), ]); ``` # ProductPriceRangeAggregation The ProductPriceRangeAggregation aggregates search results by the value of the product's price. ## Arguments - `name` - name of the Aggregation - `currencyCode` - currency code of the price - `ranges` - array of Range objects that define the borders of the specific range sets ## Example ``` $query = new ProductQuery(); $query->setAggregations([ new ProductPriceRangeAggregation('price', 'PLN', [ new \Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\Range(0, 10000), new \Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\Range(10000, null), ]), ]); ``` # ProductTypeTerm The ProductTypeTermAggregation aggregates search results by the product type. ## Arguments - `name` - name of the Aggregation object ## Example ``` $query = new ProductQuery(); $query->setAggregations([ new ProductTypeTermAggregation('product_type'), ]); ``` # Embeddings search reference Embeddings provide vector representations of content or text, enabling [semantic similarity search](https://doc.ibexa.co/en/latest/search/search_api/#search-with-embeddings). Foundational abstractions are provided for embedding-based search, while embedding providers generate vector representations. Searching with embeddings is designed for use with the [Taxonomy suggestions](https://doc.ibexa.co/en/latest/content_management/taxonomy/taxonomy/#taxonomy-suggestions) feature. The [`Ibexa\Contracts\Taxonomy\Search\Query\Value\TaxonomyEmbedding`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Taxonomy-Search-Query-Value-TaxonomyEmbedding.html) class allows embedding queries to target taxonomy data. > **Note: Feature support** > > Searching with embeddings requires a search engine that supports it, such as Elasticsearch or Solr 9.8.1+. ## Core query objects ### EmbeddingQuery - [`Ibexa\Contracts\Core\Repository\Values\Content\EmbeddingQuery`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-EmbeddingQuery.html) represents a semantic similarity search request. It encapsulates an [Embedding](#embedding) instance and supports pagination, aggregations, and result counting through the same API as standard content queries. > **Note: Embedding query properties** > > Embedding queries do not use criteria for similarity, but for additional filtering applied through the query filter. Also, embedding queries do not allow standard Query properties supported by [search engines](https://doc.ibexa.co/en/latest/search/search_engines/search_engines/index.md) other than the Legacy Search, such as `query`, `sortClauses`, or `spellcheck`. - [EmbeddingQueryBuilder](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-EmbeddingQueryBuilder.html) is a builder for constructing `EmbeddingQuery` instances. It helps construct queries consistently and integrates embedding queries with the search query pipeline. You must provide the required embedding value by using the `withEmbedding` method ### Embedding - [`Ibexa\Contracts\Core\Repository\Values\Content\Query\Embedding`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Embedding.html) represents the vector input used for similarity search. It stores embedding values as float arrays, while providers generate those vectors from text input ## Query execution Embedding queries are executed by the search engine by using the configured embedding model and provider. At runtime, the system resolves the appropriate embedding provider and ensures that the embedding vector is compatible with the configured model. Runtime validation includes validating vector dimensionality and selecting the correct indexed field for similarity search. Field selection is determined by the configured embedding model and backend specific query mapping, while vector dimensionality is validated when the query reaches the search engine. ## Embedding providers Embedding providers implement the contract for generating vector representations of input data. Out of the box, embedding search integration is provided for `TaxonomyEmbedding`. If you use a custom embedding value type, implement matching embedding visitors for your [search engine](https://doc.ibexa.co/en/latest/search/search_engines/search_engines/index.md). Otherwise, query execution may fail due to no visitor available. - [`Ibexa\Contracts\Core\Search\Embedding\EmbeddingProviderInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Search-Embedding-EmbeddingProviderInterface.html) generates embeddings for the provided text or other input - [`Ibexa\Contracts\Core\Search\Embedding\EmbeddingProviderRegistryInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Search-Embedding-EmbeddingProviderRegistryInterface.html) lists available embedding providers or gets one by its identifier - [`Ibexa\Contracts\Core\Search\Embedding\EmbeddingProviderResolverInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Search-Embedding-EmbeddingProviderResolverInterface.html) determines the embedding provider to be used for generating embeddings based on the system configuration, or a demand passed through the `resolveByModelIdentifier` method ## Configuration Models used to resolve embedding queries must be configured per SiteAccess in [system configuration](https://doc.ibexa.co/en/latest/administration/configuration/configuration/index.md). Each entry defines the model's name, vector dimensionality, the field suffix, and the embedding provider that generates vectors. Field suffixes assigned to the models must be unique, as they become part of the indexed field name. You select the default model by setting a value in the `default_embedding_model` key. ``` ibexa: system: default: embedding_models: text-embedding-3-small: name: 'text-embedding-3-small' dimensions: 1536 field_suffix: '3small' embedding_provider: 'ibexa_openai' default_embedding_model: text-embedding-ada-002 ``` For a real-life example of embedding models configuration, see [Taxonomy suggestions](https://doc.ibexa.co/en/latest/content_management/taxonomy/taxonomy/#change-embedding-generation-models-or-embedding-provider). - [EmbeddingConfigurationInterface](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Search-Embedding-EmbeddingConfigurationInterface.html) allows access to the embedding model configuration in the system (for example, list of available models, default model name, default provider, field suffix, and so on) ## Embedding fields Embedding vectors are stored in dedicated search fields. These fields can be used by the search engine to perform vector similarity comparisons when embedding queries are executed. ``` create(); echo $embeddingField->getType(); // for example, "ibexa_dense_vector_model_123" // Create a custom embedding field with a specific type $customField = $factory->create('custom_embedding_type'); echo $customField->getType(); // "custom_embedding_type" ``` Once you create a field, subscribe to the `ContentIndexCreateEvent` indexing event that [adds the field to the index](https://doc.ibexa.co/en/latest/search/extensibility/index_custom_elasticsearch_data/index.md). - [`Ibexa\Contracts\Core\Search\FieldType\EmbeddingFieldFactory`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Search-FieldType-EmbeddingFieldFactory.html) creates dedicated search fields that store embedding vectors ## Validation - [`Ibexa\Contracts\Core\Repository\Values\Content\QueryValidatorInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-QueryValidatorInterface.html) validates embedding query structure before execution # Search in trash reference When you [search for content items that are held in trash](https://doc.ibexa.co/en/latest/search/search_api/#search-in-trash), you can apply only a limited subset of Search Criteria and Sort Clauses which can be used by [`Ibexa\Contracts\Core\Repository\TrashService::findTrashItems`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-TrashService.html#method_findTrashItems). Some sort clauses are exclusive to trash search. ## Search Criteria - [ContentName](https://doc.ibexa.co/en/latest/search/criteria_reference/contentname_criterion/index.md) - [ContentTypeId](https://doc.ibexa.co/en/latest/search/criteria_reference/contenttypeid_criterion/index.md) - [DateMetadata](https://doc.ibexa.co/en/latest/search/criteria_reference/datemetadata_criterion/index.md) (which can use the additional exclusive target `DateMetadata::TRASHED`) - [MatchAll](https://doc.ibexa.co/en/latest/search/criteria_reference/matchall_criterion/index.md) - [MatchNone](https://doc.ibexa.co/en/latest/search/criteria_reference/matchnone_criterion/index.md) - [SectionId](https://doc.ibexa.co/en/latest/search/criteria_reference/sectionid_criterion/index.md) - [UserMetadata](https://doc.ibexa.co/en/latest/search/criteria_reference/usermetadata_criterion/index.md) ## Logical operators - [LogicalAnd](https://doc.ibexa.co/en/latest/search/criteria_reference/logicaland_criterion/index.md) - [LogicalNot](https://doc.ibexa.co/en/latest/search/criteria_reference/logicalor_criterion/index.md) - [LogicalOr](https://doc.ibexa.co/en/latest/search/criteria_reference/logicalor_criterion/index.md) ## Sort Clauses - [ContentName](https://doc.ibexa.co/en/latest/search/sort_clause_reference/contentname_sort_clause/index.md) - [ContentTypeName](https://doc.ibexa.co/en/latest/search/sort_clause_reference/contenttypename_sort_clause/index.md) - [DateTrashed](https://doc.ibexa.co/en/latest/search/sort_clause_reference/datetrashed_sort_clause/index.md) - [Depth](https://doc.ibexa.co/en/latest/search/sort_clause_reference/depth_sort_clause/index.md) - [Path](https://doc.ibexa.co/en/latest/search/sort_clause_reference/path_sort_clause/index.md) - [Priority](https://doc.ibexa.co/en/latest/search/sort_clause_reference/priority_sort_clause/index.md) - [SectionName](https://doc.ibexa.co/en/latest/search/sort_clause_reference/sectionname_sort_clause/index.md) - [UserLogin](https://doc.ibexa.co/en/latest/search/sort_clause_reference/userlogin_sort_clause/index.md) # Create custom Search Criterion To provide support for a custom Search Criterion, do the following. ## Create Criterion class First, create a `CameraManufacturerCriterion.php` file that contains the Criterion class: ``` 'exif_camera_manufacturer_id:"' . $this->escapeQuote((string) $value) . '"', $criterion->value ); return '(' . implode(' OR ', $expressions) . ')'; } } ``` **Elasticsearch** ``` [ 'exif_camera_manufacturer_id' => (array)$criterion->value, ], ]; } } ``` Finally, register the visitor as a service. Search Criteria can be valid for both Content and Location search. To choose the search type, use either `content` or `location` in the tag when registering the visitor as a service: **Solr** ``` services: App\Query\Criterion\Solr\CameraManufacturerVisitor: tags: - { name: ibexa.search.solr.query.content.criterion.visitor } - { name: ibexa.search.solr.query.location.criterion.visitor } ``` **Elasticsearch** ``` services: App\Query\Criterion\Elasticsearch\CameraManufacturerVisitor: tags: - { name: ibexa.search.elasticsearch.query.content.criterion.visitor } - { name: ibexa.search.elasticsearch.query.location.criterion.visitor } ``` This example pretends a new `exif_camera_manufacturer_id` data is indexed. For more information about indexing new additional data, see [Solr document field mappers](https://doc.ibexa.co/en/latest/search/extensibility/solr_document_field_mappers/index.md) or [Index custom Elasticsearch data](https://doc.ibexa.co/en/latest/search/extensibility/index_custom_elasticsearch_data/index.md). # Create custom Sort Clause To create a custom Sort Clause, do the following. ## Create Sort Clause class First, add a `ScoreSortClause.php` file with the Sort Clause class: ``` getDirection($sortClause); } } ``` **Elasticsearch** ``` direction === Query::SORT_ASC ? 'asc' : 'desc'; return [ '_score' => [ 'order' => $order, ], ]; } } ``` The `supports()` method checks if the implementation can handle the given Sort Clause. The `visit()` method contains the logic that translates Sort Clause information into data understandable by the search engine. The `visit()` method takes the Sort Clause visitor, the Sort Clause itself and the language filter as arguments. Finally, register the visitor as a service. Sort Clauses can be valid for both content and Location search. To choose the search type, use either `content` or `location` in the tag when registering the visitor as a service: **Solr** ``` services: App\Query\SortClause\Solr\ScoreVisitor: tags: - { name: ibexa.search.solr.query.content.sort_clause.visitor } - { name: ibexa.search.solr.query.location.sort_clause.visitor } ``` **Elasticsearch** ``` services: App\Query\SortClause\Elasticsearch\ScoreVisitor: tags: - { name: ibexa.search.elasticsearch.query.content.sort_clause.visitor } - { name: ibexa.search.elasticsearch.query.location.sort_clause.visitor } ``` # Create custom Aggregation ## Create aggregation class To create a custom Aggregation, create an aggregation class. In the following example, an aggregation groups the location query results by the location priority: ``` */ final class PriorityRangeAggregation extends AbstractRangeAggregation implements LocationAggregation { } ``` The `PriorityRangeAggregation` class extends `AbstractRangeAggregation`. The name of the class indicates that it aggregates the results by using the Range aggregation. An aggregation must implement the [`Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Aggregation.html) interface or inherit one of following abstract classes: - [`Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\AbstractRangeAggregation`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Aggregation-AbstractRangeAggregation.html) - [`Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\AbstractStatsAggregation`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Aggregation-AbstractStatsAggregation.html) - [`Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\AbstractTermAggregation`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Aggregation-AbstractTermAggregation.html) An aggregation can also implement one of the following interfaces: - [`Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\FieldAggregation`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Aggregation-FieldAggregation.html), based on content field - [`Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\LocationAggregation`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Aggregation-LocationAggregation.html), based on content location - [`Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\RawAggregation`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Aggregation-RawAggregation.html), based on details of the index structure > **Note: Aggregation definition** > > An aggregation definition must contain at least the name of an aggregation and optional aggregation parameters, such as, for example, the path (string) that is used to limit aggregation results to a specific subtree, content type identifier, or field definition identifier, which is mapped to the search index field name. > > Aggregation definition must be independent of the search engine used. A custom aggregation requires that the following elements are provided: - An aggregation visitor that returns an array of results - A result extractor that transforms raw aggregation results from the search engine into `AggregationResult` objects In simpler cases, you can apply one of the built-in visitors that correspond to the aggregation type. The example below uses `RangeAggregationVisitor`: **Solr** ``` services: app.search.solr.query.aggregation_visitor.priority_range_aggregation: class: Ibexa\Solr\Query\Common\AggregationVisitor\RangeAggregationVisitor factory: [ '@Ibexa\Solr\Query\Common\AggregationVisitor\Factory\SearchFieldAggregationVisitorFactory', 'createRangeAggregationVisitor' ] arguments: $aggregationClass: 'App\Query\Aggregation\Solr\PriorityRangeAggregation' $searchIndexFieldName: 'priority_i' tags: - { name: ibexa.search.solr.query.location.aggregation.visitor } ``` **Elasticsearch** ``` services: app.search.elasticsearch.query.aggregation_visitor.priority_range_aggregation: class: Ibexa\Elasticsearch\Query\AggregationVisitor\RangeAggregationVisitor factory: [ '@Ibexa\Elasticsearch\Query\AggregationVisitor\Factory\SearchFieldAggregationVisitorFactory', 'createRangeAggregationVisitor' ] arguments: $aggregationClass: 'App\Query\Aggregation\Elasticsearch\PriorityRangeAggregation' $searchIndexFieldName: 'priority_i' tags: - { name: ibexa.search.elasticsearch.query.location.aggregation.visitor } ``` The visitor is created by `SearchFieldAggregationVisitorFactory`. You provide it with two arguments: - The aggregation class in `aggregationClass` - The field name in search index in `searchIndexFieldName` In this example, the field is `priority_i` which exists only for locations. **Solr** Tag the service with `ibexa.search.solr.query.location.aggregation.visitor`. For content-based aggregations, use the `ibexa.search.solr.query.content.aggregation.visitor` tag. **Elasticsearch** Tag the service with `ibexa.search.elasticsearch.query.location.aggregation_visitor`. For content-based aggregations, use the `ibexa.search.elasticsearch.query.content.aggregation.visitor` tag. For the result extractor, you can use the built-in `RangeAggregationResultExtractor` and provide it with the aggregation class in the `aggregationClass` parameter. **Solr** Tag the service with `ibexa.search.solr.query.location.aggregation.result.extractor`. As `$keyMapper` to transform raw keys into more usable objects or scalar values, use `IntRangeAggregationKeyMapper` or create your own implementing [`RangeAggregationKeyMapper`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Solr-ResultExtractor-AggregationResultExtractor-RangeAggregationKeyMapper.html). ``` services: app.search.solr.query.aggregation_result_extractor.priority_range_aggregation: class: Ibexa\Solr\ResultExtractor\AggregationResultExtractor\RangeAggregationResultExtractor arguments: $aggregationClass: 'App\Query\Aggregation\Solr\PriorityRangeAggregation' $keyMapper: 'Ibexa\Solr\ResultExtractor\AggregationResultExtractor\RangeAggregationKeyMapper\IntRangeAggregationKeyMapper' tags: - { name: ibexa.search.solr.query.location.aggregation.result.extractor } ``` For other cases, a [`TermAggregationKeyMapper`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Solr-ResultExtractor-AggregationResultExtractor-TermAggregationKeyMapper.html) interface is also available. **Elasticsearch** Tag the service with `ibexa.search.elasticsearch.query.location.aggregation.result.extractor`. ``` services: app.search.elasticsearch.query.aggregation_result_extractor.priority_range_aggregation: class: Ibexa\Elasticsearch\Query\ResultExtractor\AggregationResultExtractor\RangeAggregationResultExtractor arguments: $aggregationClass: 'App\Query\Aggregation\Elasticsearch\PriorityRangeAggregation' tags: - { name: ibexa.search.elasticsearch.query.location.aggregation.result.extractor } ``` You can use a different type of aggregation, followed by respective visitor and extractor classes: **Solr** - Range - `Ibexa\Solr\Query\Common\AggregationVisitor\RangeAggregationVisitor` - `Ibexa\Solr\ResultExtractor\AggregationResultExtractor\RangeAggregationResultExtractor` - Stats (count, minimum, maximum, average, sum) - `Ibexa\Solr\Query\Common\AggregationVisitor\StatsAggregationVisitor` - `Ibexa\Solr\ResultExtractor\AggregationResultExtractor\StatsAggregationResultExtractor` - Term - `Ibexa\Solr\Query\Common\AggregationVisitor\TermAggregationVisitor` - `Ibexa\Solr\ResultExtractor\AggregationResultExtractor\TermAggregationResultExtractor` **Elasticsearch** - Range - `Ibexa\ElasticSearchEngine\Query\AggregationVisitor\RangeAggregationVisitor` - `Ibexa\ElasticSearchEngine\Query\ResultExtractor\AggregationResultExtractor\RangeAggregationResultExtractor` - Stats (count, minimum, maximum, average, sum) - `Ibexa\ElasticSearchEngine\Query\AggregationVisitor\StatsAggregationVisitor` - `Ibexa\ElasticSearchEngine\Query\ResultExtractor\AggregationResultExtractor\StatsAggregationResultExtractor` - Term - `Ibexa\ElasticSearchEngine\Query\AggregationVisitor\TermAggregationVisitor` - `Ibexa\ElasticSearchEngine\Query\ResultExtractor\AggregationResultExtractor\TermAggregationResultExtractor` In a more complex use case, you must create your own visitor and extractor. ### Create aggregation visitor **Solr** The aggregation visitor must implement [`Ibexa\Contracts\Solr\Query\AggregationVisitor`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Solr-Query-AggregationVisitor.html): ``` $aggregation */ public function visit( AggregationVisitor $dispatcherVisitor, Aggregation $aggregation, array $languageFilter ): array { $rangeFacets = []; foreach ($aggregation->getRanges() as $range) { $from = $this->formatRangeValue($range->getFrom()); $to = $this->formatRangeValue($range->getTo()); $rangeFacets["{$from}_{$to}"] = [ 'type' => 'query', 'q' => sprintf('priority_i:[%s TO %s}', $from, $to), ]; } return [ 'type' => 'query', 'q' => '*:*', 'facet' => $rangeFacets, ]; } private function formatRangeValue($value): string { if ($value === null) { return '*'; } return (string)$value; } } ``` **Elasticsearch** The aggregation visitor must implement [`Ibexa\Contracts\ElasticSearchEngine\Query\AggregationVisitor`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Elasticsearch-Query-AggregationVisitor.html): ``` $aggregation * * @return array> */ public function visit(AggregationVisitor $dispatcher, Aggregation $aggregation, LanguageFilter $languageFilter): array { $ranges = []; foreach ($aggregation->getRanges() as $range) { if ($range->getFrom() !== null && $range->getTo() !== null) { $ranges[] = [ 'from' => $range->getFrom(), 'to' => $range->getTo(), ]; } elseif ($range->getFrom() === null && $range->getTo() !== null) { $ranges[] = [ 'to' => $range->getTo(), ]; } elseif ($range->getFrom() !== null && $range->getTo() === null) { $ranges[] = [ 'from' => $range->getFrom(), ]; } else { // invalid range } } return [ 'range' => [ 'field' => 'priority_i', 'ranges' => $ranges, ], ]; } } ``` The `canVisit()` method checks whether the provided aggregation is of the supported type (in this case, your custom `PriorityRangeAggregation`). The `visit()` method returns an array of results. Finally, register the aggregation visitor as a service. **Solr** Tag the aggregation visitor with `ibexa.search.solr.query.location.aggregation.visitor`: ``` services: App\Query\Aggregation\Solr\PriorityRangeAggregationVisitor: tags: - { name: ibexa.search.solr.query.location.aggregation.visitor } ``` For content-based aggregations, use the `ibexa.search.solr.query.content.aggregation.visitor` tag. **Elasticsearch** Tag the aggregation visitor with `ibexa.elasticsearch.query.location.aggregation_visitor`: ``` services: App\Query\Aggregation\Elasticsearch\PriorityRangeAggregationVisitor: tags: - { name: ibexa.search.elasticsearch.query.location.aggregation.visitor } ``` For content-based aggregations, use the `ibexa.search.elasticsearch.query.content.aggregation.visitor` tag. ### Create result extractor **Solr** You must also create a result extractor, which implements [`Ibexa\Contracts\Solr\ResultExtractor\AggregationResultExtractor`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Solr-ResultExtractor-AggregationResultExtractor.html) that transforms raw aggregation results from Solr into `AggregationResult` objects: ``` $bucket) { if ($key === 'count' || !str_contains($key, '_')) { continue; } [$from, $to] = explode('_', $key, 2); $entries[] = new RangeAggregationResultEntry( new Range( $from !== '*' ? $from : null, $to !== '*' ? $to : null ), $bucket->count ); } return new RangeAggregationResult($aggregation->getName(), $entries); } } ``` The `canVisit()` method checks whether the provided aggregation is of the supported type (in this case, your custom `PriorityRangeAggregation`). The `extract()` method converts the [raw data provided by the search engine](https://solr.apache.org/guide/8_8/search-sample.html#aggregation) to a `RangeAggregationResult` object. **Elasticsearch** You must also create a result extractor, which implements [`Ibexa\Contracts\ElasticSearchEngine\Query\AggregationResultExtractor`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Elasticsearch-Query-AggregationResultExtractor.html) that transforms raw aggregation results from Elasticsearch into `AggregationResult` objects: ``` getName(), $entries); } } ``` The `supports()` method checks whether the provided aggregation is of the supported type (in this case, your custom `PriorityRangeAggregation`). The `extract()` method converts the [raw data provided by the search engine](https://www.elastic.co/docs/explore-analyze/query-filter/aggregations) to a `RangeAggregationResult` object. Finally, register the result extractor as a service. **Solr** Tag the result extractor with `ibexa.search.solr.query.location.aggregation.result.extractor`: ``` services: App\Query\Aggregation\Solr\PriorityRangeAggregationResultExtractor: tags: - { name: ibexa.search.solr.query.location.aggregation.result.extractor } ``` For content-based aggregations, use the `ibexa.search.solr.query.content.aggregation.result.extractor` tag. **Elasticsearch** Tag the result extractor with `ibexa.elasticsearch.query.location.aggregation_result_extractor`: ``` services: App\Query\Aggregation\Elasticsearch\PriorityRangeAggregationResultExtractor: tags: - { name: ibexa.search.elasticsearch.query.location.aggregation.result.extractor } ``` For content-based aggregations, use the `ibexa.search.elasticsearch.query.content.aggregation.result.extractor` tag. # Solr document field mappers You can use document field mappers to index additional data in the search engine. The additional data can come from external sources (for example, the [Personalization service](https://doc.ibexa.co/en/latest/personalization/personalization/index.md)), or from internal ones. An example of indexing internal data is indexing data through the location hierarchy: from the parent location to the child location, or indexing child data on the parent location. You can use this to find the content with full-text search, or to simplify a search in a complicated data model. To do this effectively, you must understand how the data is indexed with the Solr search engine. Solr uses [documents](https://solr.apache.org/guide/7_7/overview-of-documents-fields-and-schema-design.html#how-solr-sees-the-world) as a unit of data that is indexed. Documents are indexed per translation, as content blocks. A block is a nested document structure. When used in Ibexa DXP, a parent document represents content, and locations are indexed as child documents of the content item. To avoid duplication, full-text data is indexed on the content document only. Knowing this, you can index additional data by the following: - All block documents (meaning content and its locations, all translations) - All block documents per translation - Content documents - Content documents per translation - Location documents Additional data is indexed by implementing a document field mapper and registering it at one of the five extension points described above. You can create the field mapper class anywhere inside your bundle, as long as you register it as a Symfony service. There are three different field mappers. Each mapper implements two methods, by the same name, but accepting different arguments: - [`ContentFieldMapper`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Solr-FieldMapper-ContentTranslationFieldMapper.html) - [`::accept(Content $content)`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Solr-FieldMapper-ContentTranslationFieldMapper.html#method_accept) - [`::mapFields(Content $content)`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Solr-FieldMapper-ContentTranslationFieldMapper.html#method_mapFields) - [`ContentTranslationFieldMapper`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Solr-FieldMapper-ContentTranslationFieldMapper.html) - [`::accept(Content $content, $languageCode)`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Solr-FieldMapper-ContentTranslationFieldMapper.html#method_accept) - [`::mapFields(Content $content, $languageCode)`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Solr-FieldMapper-ContentTranslationFieldMapper.html#method_mapFields) - [`LocationFieldMapper`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Solr-FieldMapper-LocationFieldMapper.html) - [`::accept(Location $content)`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Solr-FieldMapper-LocationFieldMapper.html#method_accept) - [`::mapFields(Location $content)`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Solr-FieldMapper-LocationFieldMapper.html#method_mapFields) Mappers can be used on the extension points by registering them with the [service container](https://doc.ibexa.co/en/latest/api/php_api/php_api/#service-container) by using service tags, as follows: - All block documents - `ibexa.search.solr.field.mapper.block` - All block documents per translation - `ibexa.search.solr.field.mapper.block.translation` - Content documents - `ibexa.search.solr.field.mapper.content` - Content documents per translation - `ibexa.search.solr.field.mapper.content.translation` - Location documents - `ibexa.search.solr.field.mapper.location` The following example shows how you can index data from the parent location content, to make it available for search on the child content. The example relies on a use case of indexing webinar data on the webinar events, which are children of the webinar. The field mapper could then look like this: ``` versionInfo->contentInfo->contentTypeId === 42; } /** * @return \Ibexa\Contracts\Core\Search\Field[] */ public function mapFields(Content $content): array { $mainLocationId = $content->versionInfo->contentInfo->mainLocationId; $location = $this->locationHandler->load($mainLocationId); $parentLocation = $this->locationHandler->load($location->parentId); $parentContentInfo = $this->contentHandler->loadContentInfo($parentLocation->contentId); return [ new Search\Field( 'parent_name', $parentContentInfo->name, new Search\FieldType\StringField() ), ]; } } ``` You index text data only on the content document, therefore, you would register the service like this: ``` services: App\Search\FieldMapper\WebinarEventParentNameFieldMapper: arguments: - '@Ibexa\Contracts\Core\Persistence\Content\Handler' - '@Ibexa\Contracts\Core\Persistence\Content\Location\Handler' tags: - {name: ibexa.search.solr.field.mapper.content} ``` > **Caution: Permission issues when using Repository API in document field mappers** > > Document field mappers are low-level and expect to be able to index all content regardless of current user permissions. If you use PHP API in your custom document field mappers, apply [`sudo()`](https://doc.ibexa.co/en/latest/api/php_api/php_api/#using-sudo), or use the Persistence SPI layer as in the example above. # Index custom Elasticsearch data [Elasticsearch](https://doc.ibexa.co/en/latest/search/search_engines/elasticsearch/elasticsearch_overview/index.md) indexes content and location data out of the box. Besides what is indexed automatically, you can add additional data to the Elasticsearch index. To do so, subscribe to one of the following events: - [`Ibexa\Contracts\ElasticSearchEngine\Mapping\Event\ContentIndexCreateEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Elasticsearch-Mapping-Event-ContentIndexCreateEvent.html) - [`Ibexa\Contracts\ElasticSearchEngine\Mapping\Event\LocationIndexCreateEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Elasticsearch-Mapping-Event-LocationIndexCreateEvent.html) These events are called when the index is created for the content and location documents. You can pass the event to a subscriber which gives you access to the document that you can modify. In the following example, when an index in created for a content or a location document, the event subscriber adds a `custom_field` of the type [`StringField`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Search-FieldType-StringField.html) to the index: ``` getDocument(); $document->fields[] = new Field( 'custom_field', 'Custom field value', new StringField() ); } public function onLocationDocumentCreate(LocationIndexCreateEvent $event): void { $document = $event->getDocument(); $document->fields[] = new Field( 'custom_field', 'Custom field value', new StringField() ); } public static function getSubscribedEvents(): array { return [ ContentIndexCreateEvent::class => 'onContentDocumentCreate', LocationIndexCreateEvent::class => 'onLocationDocumentCreate', ]; } } ``` If you're not using [Symfony's autoconfiguration](https://symfony.com/doc/7.4/service_container.html#the-autoconfigure-option) for event subscribers, register it as a service: ``` services: App\EventSubscriber\CustomIndexDataSubscriber: tags: - { name: kernel.event_subscriber } ``` # Customize Elasticsearch index structure You can customize the structure of your Elasticsearch search index to manage how documents in the index are grouped. This lets you control the size of [Elasticsearch shards](https://www.elastic.co/docs/deploy-manage/production-guidance/scaling-considerations) that the index is divided into. By customizing the structure to your needs, you can avoid "oversharding" (having too many shards), which negatively affects performance and can lead to instability. For more information about adapting the size of your search index shards, see [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/8.4/size-your-shards.html). ## Selecting indexing strategy In your Elasticsearch configuration you can select one of four built-in strategies that control grouping documents in the index. The strategies are: - `NullGroupResolver` - groups all documents into a single group. - `LanguageGroupResolver` - groups documents by language code. - `ContentTypeGroupResolver`- groups documents by content type ID. - `CompositeGroupResolver` - allows combining multiple group resolves together to have a more granular index (default). The default strategy is the composite of language and content type ID, resulting in indexes in the form of `___`. To change the strategy, use the `ibexa_elasticsearch.document_group_resolver` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa_elasticsearch: document_group_resolver: 'Ibexa\Elasticsearch\ElasticSearch\Index\Group\ContentTypeGroupResolver' ``` Select the strategy based on the structure of your repository, taking into accounts data such as the number of content items, content types, or languages. ## Custom indexing strategy You can also create a group resolver that provides a custom indexing strategy. This resolver must implement `Ibexa\Contracts\Elasticsearch\ElasticSearch\Index\Group\GroupResolverInterface`. ### Create group resolver In this example, create a `ContentTypeGroupGroupResolver` based on the content type Group ID of the document: ``` contentTypeHandler->load($document->contentTypeId)->groupIds[0]; return (string)$index; } } ``` Register the resolver as a service: ``` services: App\GroupResolver\ContentTypeGroupGroupResolver: arguments: $contentTypeHandler: '@Ibexa\Contracts\Core\Persistence\Content\Type\Handler' ``` ### Configure indexing strategy Finally, in configuration indicate that Elasticsearch should use your custom indexing strategy: ``` ibexa_elasticsearch: document_group_resolver: 'App\GroupResolver\ContentTypeGroupGroupResolver' ``` # Manipulate Elasticsearch query You can customize the search query before it's executed. To do it, subscribe to [`Ibexa\Contracts\Elasticsearch\Query\Event\QueryFilterEvent`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Elasticsearch-Query-Event-QueryFilterEvent.html). The following example shows how to add a Search Criterion to all queries. Depending on your configuration, this might impact all search queries, including those used for search and content tree in the back office. ``` getQuery(); $additionalCriteria = new ObjectStateIdentifier('locked'); if ($query->filter !== null) { $query->filter = $additionalCriteria; } else { // Append Criterion to existing filter $query->filter = new LogicalAnd([ $query->filter, $additionalCriteria, ]); } } public static function getSubscribedEvents(): array { return [ QueryFilterEvent::class => 'onQueryFilter', ]; } } ``` If you're not using [Symfony's autoconfiguration](https://symfony.com/doc/7.4/service_container.html#the-autoconfigure-option) for event subscribers, register it as a service: ``` services: App\EventSubscriber\CustomQueryFilterSubscriber: tags: - { name: kernel.event_subscriber } ``` # Reindex search To (re)create or refresh the search engine index for configured search engines (per SiteAccess repository), use the `php bin/console ibexa:reindex` command. Some examples of common usage: ``` # Reindex the whole index using parallel process (by default starts by purging the whole index) # (with the 'auto' option which detects the number of CPU cores -1, default behavior) php bin/console ibexa:reindex --processes=auto # Refresh a part of the subtree (implies --no-purge) php bin/console ibexa:reindex --subtree=2 # Refresh content updated since a date (implies --no-purge) php bin/console ibexa:reindex --since=yesterday # Refresh (or delete when not found) content by IDs (implies --no-purge) php bin/console ibexa:reindex --content-ids=3,45,33 ``` For more information, see `php bin/console ibexa:reindex --help`. # Ibexa Cloud # Ibexa Cloud Ibexa Cloud is a cloud hosting platform that enables you to host your application in the cloud by using the Upsun service. - [Ibexa Cloud product guide](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/ibexa_cloud/ibexa_cloud_guide/): Learn how to host your application and improve your business processes by using Ibexa Cloud hosting platform. - [Install on Ibexa Cloud](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/ibexa_cloud/install_on_ibexa_cloud/): Install and configure Ibexa DXP to run in cloud using Ibexa Cloud. - [Environment variables on Ibexa Cloud](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/ibexa_cloud/environment_variables/): Automatically generated environment variables based on Ibexa Cloud relationships and routes. - [DDEV and Ibexa Cloud](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/ibexa_cloud/ddev_and_ibexa_cloud/): Use DDEV to run an Ibexa Cloud project locally. # Ibexa Cloud product guide ## What is Ibexa Cloud Ibexa Cloud is a cloud hosting platform that enables you to host your application in the cloud by using the [Upsun](https://upsun.com/) service. It also establishes the framework for the potential growth and improves project delivery. As a diverse Platform as a Service (PaaS), it's designed to allow you to focus on the crucial things. Ibexa Cloud is a part of Ibexa DXP - a software that is designed to provide your business with all the features, functionality, and support your need to transform your business for the digital age. *[Image: Ibexa Cloud - part of Ibexa DXP]* ## Availability Ibexa Cloud is available in all Ibexa DXP editions. ## How does Ibexa Cloud work Ibexa Cloud, as a hosting platform, is designed to streamline the development and testing processes, allowing you to deliver new features faster. It ensures that developers dedicate more time to important development activities rather than maintaining databases, queues, search engines, and operating systems. It shifts the focus away from fundamental operations and toward improving your digital services with additional features and capabilities. *[Image: How does Ibexa Cloud work]* ## Ibexa Cloud account If you want to use Ibexa Cloud, you need to check the [requirements](https://doc.ibexa.co/en/latest/getting_started/requirements/#ibexa-cloud-requirements-and-setup) and follow [installation process](https://doc.ibexa.co/en/latest/ibexa_cloud/install_on_ibexa_cloud/index.md). To use Ibexa Cloud, you must make arrangements with Ibexa to get and set up a user account. To do so, contact your Partner Manager, or Sales representatives team, or fill out the form available at this link: . When you have an account, you can log in to . ## Capabilities ### Platform as a Service Ibexa Cloud is a PaaS provider. It's a cloud-based subscription service that you can use for developing, managing, and running applications without infrastructure concerns. This cloud computing approach gives users access to a full cloud platform, including hardware, software, and infrastructure. PaaS eliminates the requirement to buy and install the necessary hardware and software. All you have to do is access it and you can start deploying resources and developing right away. *[Image: How PaaS works]* ### Performance management Make your applications more effective, scalable, and effective by using the [Observability Suite](https://docs.upsun.com/increase-observability.html). This package gives you the ability to test, profile, and monitor your application before putting it into production. Observability Suite comes with each Ibexa Cloud subscription. ### Automation Ibexa Cloud automates time-consuming testing processes and encourages ongoing QA testing, so your projects are ready for deployment much faster. For further protection and simple rollbacks, it also automates your backup procedures. This guarantees that the developers are working within a framework that is adaptable, safe, and responsive to their requirements. Thanks to built-in CI/CD features, you can also reduce the need for manual testing and accelerate your development. *[Image: Ibexa Cloud - effort]* ### Digital tool Ibexa Cloud is a digital tool - it's a perfect starting step toward your company's digital transformation. What's more, Ibexa Cloud as an end-to-end cloud hosting platform, it's scalable, requires little up-front expenditure, and no ongoing maintenance. Ibexa Cloud as a PaaS solution enables you to start constructing effective digital systems that provide great consumer experiences. ## Benefits ### Safety and simplicity Comprehensive data security procedures guarantee that you maintain complete ownership of your client's data, defining where it's stored. You can be sure that the safety protocols are compliant with all applicable legislation. What's more, all updates to your code and infrastructure are fully auditable. Global CDN (Content Delivery Network) is included and fully managed. Ibexa Cloud, thanks to extensive Ibexa support, enables effortless deployment. You can create a clone not only of the code, but also data and the infrastructure. As the infrastructure is exactly the same as what's currently in production, you can be sure that everything works well when you conduct your release and push it live. ### High availability and compatibility Ibexa Cloud is compatible with your choice of public cloud server and supports a variety of hosting platforms. It's a Git-native development - compatible with Git Flow. Ibexa Cloud deployment integrates naturally at the end of your existing production chain, including staging, and work in progress branch preview. You can also integrate with, for example, Bitbucket, GitHub, GitLab. You can instantly clone every branch of both your code and infrastructure configuration. Upsun (and Ibexa Cloud, by extension) uses the [Infrastructure as Code approach](https://fixed.docs.upsun.com/learn/overview.html#infrastructure-as-code). It means that the infrastructure is described in the code and that is what allows you to clone both code and infrastructure configuration at the same time. If you want to work with services such as [MySQL](https://fixed.docs.upsun.com/add-services/mysql.html) or [Elasticsearch](https://fixed.docs.upsun.com/add-services/elasticsearch.html), you can add them with a line of code. What's more, you can run in your chosen cloud, like Microsoft Azure, Orange, or Google Cloud Platform. ### Great customer experience The Ibexa Cloud as a PaaS solution enables your company to begin developing effective digital systems that provide exceptional client experiences. It gives the user access to all of Ibexa DXP's features while hosting them in a cloud environment. ### Increase in developer productivity Thanks to Ibexa Cloud, there is around 40% increase in developer productivity, 15% faster user acceptance testing, and 20% more deployments. Developer can focus more on important development activities. Apps can be developed and hosted more quickly and without the risk of infrastructure-related delays. ### A single provider to manage Ibexa Cloud, as a hosting infrastructure, comes together with a software provided by Ibexa DXP in a single package. Management thus becomes much easier. ### Scalability and flexibility Ibexa Cloud allows for effortless scaling of resources to meet changing workload demands, ensuring high availability and performance. ### Marketing friendly tool With instant previews, the marketing team can collaborate better with developers. Team members can also see and provide input on the new feature's actual appearance. ### Integrated customer support Support integration is a part of the combined Upsun and Ibexa service. In case of an issue, you only have to submit one ticket, no matter whether it has to do with Ibexa DXP or the cloud infrastructure. When you submit a ticket with Ibexa Cloud, the support team looks into the issue and assigns it to the appropriate expert. ### Lower cost With PaaS solution there is no need to purchase and maintain hardware or software infrastructure. This reduces the total cost of ownership and operational expenses. According to [Forrester Total Economic Impact report](https://devcenter.upsun.com/posts/platform-sh-drives-meaningful-cost-savings/) from March 2022, a company that uses Upsun for three years achieves an investment return of 219%. According to this in-depth analysis, Upsun reduces operating expenses for developers and IT by $1 million over the course of three years, and in as little as seven months, break-even point can be reached. # Install on Ibexa Cloud Ibexa Cloud enables you to host your application in the cloud by using the [Upsun](https://upsun.com/) service. ## 1. Prepare configuration files If you didn't add cloud configuration during installation, run the following commands now: ``` composer require ibexa/cloud php bin/console ibexa:cloud:setup --upsun ``` These commands add the necessary package and configuration files required for Ibexa Cloud. You can adapt the configuration in the following places: - `.platform.app.yaml` - main configuration - `.platform/services.yml` - additional services such as search engines or cache - `.platform/routes.yml` - routes to additional services, for example Fastly For details about available configuration settings, refer to [Upsun documentation](https://fixed.docs.upsun.com/create-apps.html). ### Disk space The total disk space depends on your Ibexa Cloud subscription level. You can assign disk space to the main app container under the `disk` key. You can distribute the remaining space between other containers (for example, the database) or search engine in `.platform/services.yaml`, under the individual service definitions. ### Build and deploy process Configuration under `hooks` defines the process of building and deploying your project. > **Note: Note** > > During the build phase (defined in the `hooks.build` configuration), files in the project have read/write permissions (can be modified). > > During deployment (defined in the `hooks.deploy` configuration), all files in the project are read-only. ### Additional services `.platform/services.yaml` contains preconfigured setting blocks that you can uncomment to enable services such as Solr or Elasticsearch, or persistent Redis session storage. For information about available services, see [Upsun documentation](https://fixed.docs.upsun.com/add-services.html#available-services). If you enable any of the services, you must uncomment the relevant relationship under the `relationship` key in `.platform.app.yaml` as well. For information about environment variables automatically generated based on your service configuration, see [Environment variables on Ibexa Cloud](https://doc.ibexa.co/en/latest/ibexa_cloud/environment_variables/index.md). ## 2. Create an account Log in to or create an account if you don't have one yet. Create a project and select its region. > **Caution: Caution** > > Don't use (or former ) which don't list Ibexa Cloud projects. Use to manage your Ibexa Cloud projects. ## 3. Prepare for hosting After the project is created, the website walks you through preparing your project for hosting. This includes adding an SSH key, and adding Upsun as a git remote. Add your Composer authentication token to the project before pushing it to Upsun. You can set this token as an environment variable. When you do, make sure the **Visible during runtime** box in Ibexa Cloud configuration is unchecked. This ensures that the token isn't exposed. ### Composer authentication using the web console In **Settings** (top right gear icon) -> **Project Settings** -> **Variables** -> **+ Create variable** *[Image: Setting token to be invisible during runtime]* ### Composer authentication using the CLI command ``` ibexa_cloud variable:create --level project --name env:COMPOSER_AUTH \ --json true --visible-runtime false --sensitive true --visible-build true \ --value '{"http-basic": {"updates.ibexa.co": {"username": "", "password": ""}}}' ``` ## 4. Push the project When you're done with configuration, push your project to the Upsun remote: ``` git push -u main ``` You can also use the [Ibexa Cloud CLI](https://cli.ibexa.cloud/) to push your code. ``` ibexa_cloud push main ``` The [database installer](https://doc.ibexa.co/en/latest/getting_started/install_ibexa_dxp/#create-a-database) runs in non-interactive mode and keeps the default password for the `admin` user. Modify this password after the installation, for example, by using [data migrations](https://doc.ibexa.co/en/latest/content_management/data_migration/importing_data/#users) or the [user management command](https://doc.ibexa.co/en/latest/users/update_basic_user_data/#change-password). > **Note: Note** > > `main` is the Upsun name for the production branch. > **Caution: Caution** > > Don't use Upsun CLI (`upsun`), instead, use the [Ibexa Cloud CLI (`ibexa_cloud`)](https://cli.ibexa.cloud/). > > To install Ibexa Cloud CLI, follow "Installation instructions". > > Ibexa Cloud CLI and Upsun CLI share the same commands and the [same documentation](https://fixed.docs.upsun.com/administration/cli.html#3-use), but you have to replace `upsun` with `ibexa_cloud`. > > If you have previously set up an alias to use Upsun CLI with Ibexa Cloud, it's outdated. Remove the alias and install Ibexa Cloud CLI instead. # Environment variables on Ibexa Cloud Ibexa Cloud automatically generates environment variables based on the configuration of relationships and routes in Upsun. It parses `PLATFORM_RELATIONSHIPS` and `PLATFORM_ROUTES` environment variables and exposes them as application-specific variables. Environment variable prefixes are created by converting relationship names to uppercase and replacing hyphens with underscores. When multiple endpoints are defined for a single relationship, numerical indices are used for all entries except the first one, for example: `SOLR`, `SOLR_1`, `SOLR_2`. When multiple services of the same type are present, environment variables are exposed for each service accordingly based on their relationship names. > **Warning: Environment variables in configuration files** > > To prevent Symfony container initialization failures, you must define placeholder values for Ibexa Cloud environment variables in your `.env` file when referencing them in configuration files. > > Do it only for the variables that are required for the Symfony container to build. > > For example, if your `doctrine.yaml` uses a [database variable](#database-variables) created for a relationship named `pgsql`: > > ``` > doctrine: > dbal: > url: '%env(resolve:PGSQL_URL)%' > ``` > > You must define a placeholder value in `.env`: > > ``` > PGSQL_URL="placeholder" > ``` > > The actual value of the environment variable is provided by Ibexa Cloud at runtime. The placeholder in `.env` is only required to prevent Symfony container compilation errors during build. ## Relationship naming conventions You can choose relationship names freely in `.platform.app.yaml` for most services. The only required names are: - `dfs_database` - DFS database (required for DFS functionality) - `redissession` or `valkeysession` - Redis/Valkey for sessions (required for dedicated session storage) Common relationship name include: - `database` - main application database - `rediscache` - Redis for cache - `elasticsearch` - Elasticsearch search service - `solr` - Solr search service ## Database variables For MySQL and PostgreSQL databases, the following variables are generated based on the relationship name (for example, `database`): - `{RELATIONSHIP_NAME}_URL` - full database URL with charset and server version - `{RELATIONSHIP_NAME}_USER` / `{RELATIONSHIP_NAME}_USERNAME` - database user - `{RELATIONSHIP_NAME}_PASSWORD` - database password - `{RELATIONSHIP_NAME}_HOST` - database host - `{RELATIONSHIP_NAME}_PORT` - database port - `{RELATIONSHIP_NAME}_NAME` / `{RELATIONSHIP_NAME}_DATABASE` - database name - `{RELATIONSHIP_NAME}_DRIVER` - database driver - `{RELATIONSHIP_NAME}_SERVER` - database server For example, for a relationship called `database`, the environment variables are named `DATABASE_URL`, `DATABASE_HOST`, `DATABASE_USER`, etc. For more information about database configuration, see [Databases](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/databases/index.md). ## DFS database variables When using [distributed file storage (DFS) that uses a separate database](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/clustering/clustering/#dfs-io-handler), you must use the relationship name `dfs_database`. In addition to the database variables listed above, additional DFS-specific variables are available when `PLATFORMSH_DFS_NFS_PATH` is set: - `DFS_NFS_PATH` - NFS path for DFS storage - `DFS_DATABASE_CHARSET` - database character set - `DFS_DATABASE_COLLATION` - database collation ## Cache variables For Redis and Valkey cache services, you can use the following variables: - `{RELATIONSHIP_NAME}_URL` - `{RELATIONSHIP_NAME}_HOST` - `{RELATIONSHIP_NAME}_PORT` - `{RELATIONSHIP_NAME}_SCHEME` In addition, you can use the following global variables: - `CACHE_POOL` - `cache.redis` for both Redis and Valkey - `CACHE_DSN` - cache connection string For more information about persistence cache configuration, see [Persistence cache](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/persistence_cache/index.md). ## Session variables For Redis-based session storage, the following variables are available. - `SESSION_HANDLER_ID` - session handler class name - `SESSION_SAVE_PATH` - Redis connection in `host:port` format The system looks for relationships named `redissession` or `valkeysession` first. If not found, it uses the first available Redis-compatible service. For more information about session configuration, see [Sessions](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/sessions/index.md). ## Search engine variables ### Solr For Solr search engine configuration, you can use the following variables: - `SEARCH_ENGINE` - set to `solr` - `SOLR_DSN` - Solr connection string - `SOLR_CORE` - Solr core name - `{RELATIONSHIP_NAME}_HOST` - `{RELATIONSHIP_NAME}_PORT` - `{RELATIONSHIP_NAME}_NAME` / `{RELATIONSHIP_NAME}_DATABASE` For more information, see [Solr search engine](https://doc.ibexa.co/en/latest/search/search_engines/solr_search_engine/solr_overview/index.md). ### Elasticsearch For Elasticsearch search engine configuration, you can use the following variables: - `SEARCH_ENGINE` - set to `elasticsearch` - `ELASTICSEARCH_DSN` - Elasticsearch connection string - `{RELATIONSHIP_NAME}_URL` - `{RELATIONSHIP_NAME}_HOST` - `{RELATIONSHIP_NAME}_PORT` - `{RELATIONSHIP_NAME}_SCHEME` For more information, see [Elasticsearch](https://doc.ibexa.co/en/latest/search/search_engines/elasticsearch/elasticsearch_overview/index.md). ## HTTP cache variables (Varnish) For Varnish-based HTTP caching, the following variables are available. - `HTTPCACHE_PURGE_TYPE` - set to `varnish` - `HTTPCACHE_PURGE_SERVER` - Varnish server address - `HTTPCACHE_VARNISH_INVALIDATE_TOKEN` - token for cache invalidation For more information about HTTP cache and Varnish configuration, see [HTTP cache](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/http_cache/http_cache/index.md). # DDEV and Ibexa Cloud Two ways are available to run an Ibexa Cloud project locally with DDEV: - [by using the `ddev-upsun` and `ddev-ibexa-cloud` add-ons](#with-ibexa-cloud-add-ons) - [like other existing project, without these add-ons](#without-ibexa-cloud-add-ons). > **Note: Note** > > The following examples use [Ibexa Cloud CLI (`ibexa_cloud`)](https://cli.ibexa.co/). ## With Ibexa Cloud add-ons To configure [`ddev/ddev-upsun` add-on](https://github.com/ddev/ddev-upsun) and [`ddev/ddev-ibexa-cloud` add-on](https://github.com/ddev/ddev-ibexa-cloud), you need a [Upsun API Token](https://fixed.docs.upsun.com/administration/cli/api-tokens.html). The `ddev/ddev-upsun` add-on configures the document root, the PHP version, the database, and the cache pool according to the Ibexa Cloud configuration. About the search engine, the add-on can configure Elasticsearch but can't configure Solr. If you use Solr on Ibexa Cloud and want to add it to your DDEV stack, see [Clustering with DDEV and `ibexa/ddev-solr` add-on](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/clustering/clustering_with_ddev/#solr). The `ddev/ddev-ibexa-cloud` add-on integrates the `ibexa_cloud` command inside the container, and eases the pull of cloud contents into the local installation. `env:COMPOSER_AUTH` from Upsun can't be used, because JSON commas are incorrectly interpreted by `--web-environment-add`, which sees them as multiple variable separators. But the variable must exist for Upsun `hooks` scripts to work. To use an `auth.json` file for this purpose, see [Using `auth.json`](https://doc.ibexa.co/en/latest/getting_started/install_with_ddev/#using-authjson). The following sequence of commands: 1. Downloads the Ibexa Cloud project from the default environment "production" into a new directory (for example `my-ddev-project`), using the [`ibexa_cloud` command](https://cli.ibexa.co/). (Replace `` with the hash of your own project. See [`ibexa_cloud help get`](https://fixed.docs.upsun.com/administration/cli.html#3-use) for options like selecting another environment). 2. Configures a new DDEV project. 3. Configures the `ddev/ddev-ibexa-cloud` add-on with ``, environment name (for example, `production`), and application name (for example, `app` from `name: app` line in `.platform.app.yaml` file). 4. Configures `ibexa_cloud` command token. See [Create an API token](https://fixed.docs.upsun.com/administration/cli/api-tokens.html#2-create-an-api-token) for more information. 5. Ignores `.ddev/` directory from Git. (Some DDEV config could be committed like in [this documentation](https://ddev.readthedocs.io/en/latest/users/extend/customization-extendibility/#extending-configyaml-with-custom-configyaml-files).) 6. Sets Composer authentication by using an already existing `auth.json` file. 7. Installs the `ddev/ddev-upsun` add-on which prompts for the Upsun API token, project ID and environment name. 8. Changes `maxmemory-policy` from default `allkeys-lfu` to a [value accepted by the `RedisTagAwareAdapter`](https://github.com/symfony/cache/blob/5.4/Adapter/RedisTagAwareAdapter.php#L95). (Check `.ddev/config.upsun.yaml` and adapt if needed. For example, you may have to comment out New Relic.) 9. Installs the `ddev/ddev-ibexa-cloud` add-on. 10. Starts the project. 11. Gets the content from Ibexa Cloud, both database and binary files by using `ddev pull ibexa-cloud` feature from the add-on. 12. Displays information about the project services. 13. Opens the project in a browser. ``` ibexa_cloud project:get my-ddev-project && cd my-ddev-project ddev config --project-type=php --php-version 8.3 --web-environment-add COMPOSER_AUTH='',DATABASE_URL=mysql://db:db@db:3306/db ddev config --web-environment-add IBEXA_PROJECT=,IBEXA_ENVIRONMENT=production,IBEXA_APP=app ddev config --web-environment-add IBEXA_CLI_TOKEN= echo '.ddev/' >> .gitignore mkdir -p .ddev/homeadditions/.composer && cp /auth.json .ddev/homeadditions/.composer ddev add-on get ddev/ddev-upsun sed -i 's/maxmemory-policy allkeys-lfu/maxmemory-policy volatile-lfu/' .ddev/redis/redis.conf ddev add-on get ddev/ddev-ibexa-cloud ddev start ddev pull ibexa-cloud -y ddev describe ddev launch ``` > **Note: Note** > > The Upsun API token is set at user profile level, therefore it's stored globally under current user root as `PLATFORMSH_CLI_TOKEN` in `~/.ddev/global_config.yaml`. ## Without Ibexa Cloud add-ons The following example adapts the [manual method to run an already existing project](https://doc.ibexa.co/en/latest/getting_started/install_with_ddev/#run-an-already-existing-project) to the Upsun case: The following sequence of commands: 1. Downloads the Ibexa Cloud Upsun project from the default environment "production" into a new directory, using the [Ibexa Cloud CLI](https://cli.ibexa.co/). (Replace `` with the hash of your own project. See [`ibexa_cloud help get`](https://fixed.docs.upsun.com/administration/cli.html#3-use) for options like selecting another environment). 2. Configures a new DDEV project. 3. Ignores `.ddev/` directory from Git. (Some DDEV config could be committed like in [this documentation](https://ddev.readthedocs.io/en/latest/users/extend/customization-extendibility/#extending-configyaml-with-custom-configyaml-files).) 4. Starts the DDEV project. 5. Sets Composer authentication. 6. [Gets the database content from Upsun](https://fixed.docs.upsun.com/add-services/mysql.html#exporting-data). 7. [Imports this database content into DDEV project's database](https://ddev.readthedocs.io/en/latest/users/usage/database-management/#database-imports). 8. [Downloads the Upsun public/var locally](https://fixed.docs.upsun.com/development/file-transfer.html#transfer-a-file-from-a-mount) to have the content binary files. 9. Install the dependencies and run post-install scripts. 10. Displays information about the project services. 11. Opens the DDEV project in a browser. ``` ibexa_cloud project:get my-ddev-project && cd my-ddev-project ddev config --project-type=php --php-version 8.3 --docroot=public --web-environment-add DATABASE_URL=mysql://db:db@db:3306/db echo '.ddev/' >> .gitignore ddev start ddev composer config --global http-basic.updates.ibexa.co ibexa_cloud db:dump --gzip --file=production.sql.gz ddev import-db --file=production.sql.gz && rm production.sql.gz ibexa_cloud mount:download --mount public/var --target public/var ddev composer install ddev describe ddev launch ``` From there, services can be added to get closer to Ibexa Cloud architecture. `.platform/services.yaml` indicates the services used. For more information, see [Clustering with DDEV](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/clustering/clustering_with_ddev/index.md). # Infrastructure and maintenance # Infrastructure and maintenance - [Cache](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/infrastructure_and_maintenance/cache/cache/): For caching, Ibexa DXP offers both HTTP cache for content views, and persistence cache. - [Clustering](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/infrastructure_and_maintenance/clustering/clustering/): Clustering enables you to host one installation of Ibexa DXP on multiple servers. - [Performance](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/infrastructure_and_maintenance/performance/): Ensure that your Ibexa DXP installation performs well by following our set of recommendations. - [Background tasks](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/infrastructure_and_maintenance/background_tasks/): Use Ibexa Messenger to run processes in the background and conserve system resources. - [Databases](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/infrastructure_and_maintenance/databases/): Ibexa DXP can use MySQL, PostgreSQL or MariaDB as its database. - [Environments](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/infrastructure_and_maintenance/environments/): In Ibexa DXP you can use environment provided by Symfony in virtual host configuration, and to create custom environments. - [Support and maintenance FAQ](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/infrastructure_and_maintenance/support_and_maintenance_faq/): See how you can resolve common issues and report a Customer Support ticket. # Request lifecycle: from request to response ## Beginning of HTTP request When entering the server infrastructure, the HTTP request can be handled by several component such as a firewall, a load balancer, or a reverse proxy before arriving on the web server itself. For an overview of what happens on a reverse proxy like Varnish or Fastly, see [Context-aware HTTP cache / Request lifecycle](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/http_cache/context_aware_cache/#request-lifecycle). When arriving at a web server, the request is filtered by Apache virtual host, Nginx Server Blocks, or equivalent. There, requests of static resources are separated from requests to PHP interpreter. As Ibexa DXP is a Symfony application, the handling of requests starts like in Symfony (see [Symfony and HTTP Fundamentals](https://symfony.com/doc/7.4/introduction/http_fundamentals.html)). If the HTTP request is to be treated by Ibexa DXP, it goes to the `public/index.php` of the [Symfony Front Controller](https://symfony.com/doc/7.4/configuration/front_controllers_and_kernel.html#the-front-controller). The front controller transforms the HTTP request into a PHP [`Request` object](https://symfony.com/doc/7.4/introduction/http_fundamentals.html#symfony-request-object) and passes it to Symfony's Kernel to get a [`Response` object](https://symfony.com/doc/7.4/introduction/http_fundamentals.html#symfony-response-object) that is transformed and sent back as an HTTP response. The schemas start with a regular `Request` object from a browser that enters Symfony and Ibexa DXP. There is no ESI, no REST, and no GraphQL request performed. ## Lifecycle flowcharts ### Concept flowchart The chart below introduces the logic of the request treatment. *[Image: Simplified request lifecycle flowchart]* ### Kernel events flowchart The following chart shows events, listeners and attributes added to the request or its wrapping event object. *[Image: Detailed request lifecycle flowchart organised around kernel events]* This schema is described below event by event. > **Tip: Tip** > > To list all listeners that listen to an event, run `php bin/console debug:event-dispatcher `, for example: > > ``` > php bin/console debug:event-dispatcher kernel.request > ``` > > To view details of a service (including class, arguments and tags), run `php bin/console debug:container --show-arguments `, for example: > > ``` > php bin/console debug:container --show-arguments ibexa.siteaccess_match_listener` > ``` > > To list all services with a specific tag, run `php bin/console debug:container --tag=`, for example: > > ``` > php bin/console debug:container --tag=router > ``` ## Kernel's request event When the request enters the Symfony's kernel (and goes underneath the [`HttpKernel`](https://symfony.com/doc/7.4/components/http_kernel.html), `http_kernel`), a `kernel.request` event (`KernelEvents::REQUEST`) is dispatched. Several listeners are called in decreasing priority. ### SiteAccess matching The [`FragmentListener`](https://github.com/symfony/http-kernel/blob/5.3/EventListener/FragmentListener.php) (priority 48) handles the request first, and then it passes to the `ibexa.siteaccess_match_listener` service (priority 45). This service can be either: - purely the `SiteAccessMatchListener` or - its `UserContextSiteAccessMatchSubscriber` decoration when HTTP cache is used. The `ibexa.siteaccess_match_listener` service: - finds the current SiteAccess using the `SiteAccess\Router` (`Ibexa\Core\MVC\Symfony\SiteAccess\Router`) regarding the [SiteAccess Matching configurations](https://doc.ibexa.co/en/latest/multisite/siteaccess/siteaccess_matching/index.md), - adds the current SiteAccess to the `Request` object's **`siteaccess`** attribute, - then dispatches the `Ibexa\Core\MVC\Symfony\SiteAccess` event (`MVCEvents::SITEACCESS`). The `SiteAccessListener` (`Ibexa\Bundle\Core\EventListener\SiteAccessListener`) subscribes to this `Ibexa\Core\MVC\Symfony\SiteAccess` event with top priority (priority 255). The `SiteAccessListener` adds the **`semanticPathinfo`** attribute, the path without SiteAccess indications ([`URIElement`](https://doc.ibexa.co/en/latest/multisite/siteaccess/siteaccess_matching/#urielement), [`URIText`](https://doc.ibexa.co/en/latest/multisite/siteaccess/siteaccess_matching/#uritext), or [`Map\URI`](https://doc.ibexa.co/en/latest/multisite/siteaccess/siteaccess_matching/#mapuri) implementing the `URILexer` interface) to the request. ### Routing Finally, the `Symfony\Component\HttpKernel\EventListener\RouterListener` (`router_listener`) (priority 32), which also listens to the `kernel.request` event, calls `Ibexa\Core\MVC\Symfony\Routing\ChainRouter::matchRequest` and adds its returned parameters to the request. #### `ChainRouter` The [`ChainRouter`](https://symfony.com/bundles/CMFRoutingBundle/current/routing-component/chain.html) is a Symfony Content Management Framework (CMF) component. Ibexa DXP makes it a service named `Ibexa\Core\MVC\Symfony\Routing\ChainRouter`. It has a collection of prioritized routers where to find one matching the request. The `ChainRouter` router collection is built by the `ChainRoutingPass`, collecting the services tagged `router`. The `DefaultRouter` is always added to the collection with top priority (priority 255). #### `DefaultRouter` `DefaultRouter` (`router.default`): The `DefaultRouter` tries to match the `semanticPathinfo` against routes, close to [the way pure Symfony does](https://symfony.com/doc/7.4/routing.html), by extending and using `Symfony\Component\Routing\Router`. If a route matches, the controller associated with it is responsible for building a `View` or `Response` object. ### `UrlWildcardRouter` `UrlWildcardRouter` (`ezpublish.urlwildcard_router`): If [URL Wildcards](https://doc.ibexa.co/en/latest/content_management/url_management/url_management/#url-wildcards) have been enabled, then the `URLWildcardRouter` is the next router tried. If a wildcard matches, the request's `semanticPathinfo` is updated and the router throws a `ResourceNotFoundException` to continue with the `ChainRouter` collection's next entry. ### `UrlAliasRouter` `UrlAliasRouter` (`Ibexa\Bundle\Core\Routing\UrlAliasRouter`): This router uses the `UrlAliasService` to associate the `semanticPathinfo` to a location. If it finds a location, the request receives the attributes **`locationId`** and **`contentId`**, **`viewType`** is set to `full`, and the **`_controller`** is set to `ibexa_content::viewAction` for now. The `locale_listener` (priority 16) sets the request's **`_locale`** attribute. > **Note: Permission control** > > Another `kernel.request` event listener is the `Ibexa\AdminUi\EventListener\RequestListener` (priority 13). When a route gets a `siteaccess_group_whitelist` parameter, this listener checks that the current SiteAccess is in one of the listed groups. For example, the back office sets an early protection of its routes by passing them a `siteaccess_group_whitelist` containing only the `admin_group`. Now, when the `Request` knows its controller, the `HttpKernel` dispatches the `kernel.controller` event. ## Kernel's controller event ### View building and matching When HttpKernel dispatches the `kernel.controller` event, the following things happen. Listening to `kernel.controller`, the `ViewControllerListener` (`Ibexa\Bundle\Core\EventListener\ViewControllerListener`) (priority 10) checks if the `_controller` request attribute is associated with a `ViewBuilder` (a service tagged `ibexa.view.builder`) in the `ViewBuilderRegistry` (`Ibexa\Core\MVC\Symfony\View\Builder\Registry\ControllerMatch`). The `ContentViewBuilder` (`Ibexa\Core\MVC\Symfony\View\Builder\ContentViewBuilder`) matches on controller starting with `ibexa_content:` (see `Ibexa\Core\MVC\Symfony\View\Builder\ContentViewBuilder::matches`). The `ContentViewBuilder` builds a `ContentView`. First, the `ContentViewBuilder` loads the `Location` and the `Content`, and adds them to the `ContentView` object. > **Caution: Permission control** > > `content/read` and/or `content/view_embed` permissions are controlled during this `ContentView` building. Then, the `ContentViewBuilder` passes the `ContentView` to its `View\Configurator` (`Ibexa\Core\MVC\Symfony\View\Configurator\ViewProvider`). It's implemented by the `View\Configurator\ViewProvider` and its `View\Provider\Registry`. This registry receives the services tagged `ibexa.view.provider` thanks to the `ViewProviderPass`. Among the view providers, the services using the `Ibexa\Bundle\Core\View\Provider\Configured` have an implementation of the `MatcherFactoryInterface` (`ibexa.content_view.matcher_factory`). Through service decoration and class inheritance, the `ClassNameMatcherFactory` is responsible for the [view matching](https://doc.ibexa.co/en/latest/templating/templates/template_configuration/#view-rules-and-matching). The `View\Configurator\ViewProvider` uses the matched view rule to add possible **`templateIdentifier`** and **`controllerReference`** to the `ContentView` object. The `ViewControllerListener` adds the ContentView to the `Request` as the **`view`** attribute. The `ViewControllerListener` eventually updates the request's `_controller` attribute with the `ContentView`'s `controllerReference`. The `HttpKernel` then dispatches a `kernel.controller_arguments` (`KernelEvents::CONTROLLER_ARGUMENTS`) but nothing from Ibexa DXP is listening to it. ## Controller execution The `HttpKernel` extracts from the request the controller and the arguments to pass to the controller. [Argument resolvers](https://symfony.com/doc/7.4/controller/value_resolver.html) work in a way similar to autowiring. The `HttpKernel` executes the controller with those arguments. As a reminder, the controller and its argument can be: - A controller set by the matched route and the request as its argument. - The default `ibexa_content::viewAction` controller and a `ContentView` as its argument. - A [custom controller](https://doc.ibexa.co/en/latest/templating/queries_and_controllers/controllers/index.md) set by the matched view rule and a `View` or the request as its argument (most likely a `ContentView` but there is no restriction). > **Caution: Permission control** > > See [Permissions for custom controller](https://doc.ibexa.co/en/latest/permissions/permission_overview/#permissions-for-custom-controllers). ## Kernel's view event and `ContentView` rendering If the controller returns something other than `Response`, the `HttpKernel` dispatches a `kernel.view` event (`KernelEvents::VIEW`). In the case of a URL Alias, the controller most likely returns a ContentView. The `ViewRendererListener` (`Ibexa\Bundle\Core\EventListener\ViewRendererListener`) uses the `ContentView` and the `TemplateRenderer` (`Ibexa\Core\MVC\Symfony\View\Renderer\TemplateRenderer`) to get the content of the `Response` and attach this new `Response` to the event. The `HttpKernel` retrieves the response attached to the event and continues. ## Summary ### Summary of events and services - event=`kernel.request` - 45:`ibexa.siteaccess_match_listener` - `Ibexa\Core\MVC\Symfony\SiteAccess\Router` - event=`Ibexa\Core\MVC\Symfony\SiteAccess` - 255:`Ibexa\Bundle\Core\EventListener\SiteAccessListener` - 32:`router_listener` - `Ibexa\Core\MVC\Symfony\Routing\ChainRouter` - tag=`router` - `router.default` - `ezpublish.urlwildcard_router` - `Ibexa\Bundle\Core\Routing\UrlAliasRouter` - 16:`locale_listener` - 13:`Ibexa\AdminUi\EventListener\RequestListener` - event=`kernel.controller` - 10:`Ibexa\Bundle\Core\EventListener\ViewControllerListener` - `Ibexa\Core\MVC\Symfony\View\Builder\Registry\ControllerMatch` - tag=`ibexa.view.builder` - `Ibexa\Core\MVC\Symfony\View\Builder\ContentViewBuilder` - `Ibexa\Core\MVC\Symfony\View\Configurator\ViewProvider` - event=`kernel.controller_arguments` - event=`kernel.view` - 0:`Ibexa\Bundle\Core\EventListener\ViewRendererListener` - `Ibexa\Core\MVC\Symfony\View\Renderer\TemplateRenderer` - event=`kernel.response` - event=`kernel.terminate` - 0:`Ibexa\Bundle\Core\EventListener\BackgroundIndexingTerminateListener` ### Examples request attributes timeline | Event | Service | Request attribute | Example | | ------------------------------------- | ------------------------------------------------------------ | ------------------------ | ----------------------------------------- | | | http_kernel | pathInfo | /en/about | | kernel.request | ibexa.siteaccess_match_listener | siteaccess | en | | Ibexa\\Core\\MVC\\Symfony\\SiteAccess | Ibexa\\Bundle\\Core\\EventListener\\SiteAccessListener | semanticPathinfo | /about | | kernel.request | router.default | \_route | N/A | | kernel.request | router.default | \_controller | N/A | | kernel.request | Ibexa\\Bundle\\Core\\Routing\\UrlAliasRouter | \_route | ibexa.url.alias | | kernel.request | Ibexa\\Bundle\\Core\\Routing\\UrlAliasRouter | \_controller | \*\*ibexa_content::\*\*viewAction | | kernel.request | Ibexa\\Bundle\\Core\\Routing\\UrlAliasRouter | viewType | full | | kernel.request | Ibexa\\Bundle\\Core\\Routing\\UrlAliasRouter | contentId | 1 | | kernel.request | Ibexa\\Bundle\\Core\\Routing\\UrlAliasRouter | locationId | 42 | | kernel.request | locale_listener | \_locale | en_GB | | kernel.controller | Ibexa\\Core\\MVC\\Symfony\\View\\Builder\\ContentViewBuilder | view.content | Content | | kernel.controller | Ibexa\\Core\\MVC\\Symfony\\View\\Builder\\ContentViewBuilder | view.location | Location | | kernel.controller | Ibexa\\Core\\MVC\\Symfony\\View\\Configurator\\ViewProvider | view.templateIdentifier | @IbexaCore/default/content/full.html.twig | | kernel.controller | Ibexa\\Core\\MVC\\Symfony\\View\\Configurator\\ViewProvider | view.controllerReference | null | | kernel.controller | Ibexa\\Bundle\\Core\\EventListener\\ViewControllerListener | view | ContentView | | kernel.controller | Ibexa\\Bundle\\Core\\EventListener\\ViewControllerListener | \_controller | ibexa_content::viewAction | | (controller execution) | http_kernel | | ContentView | | kernel.view | Ibexa\\Bundle\\Core\\EventListener\\ViewRendererListener | response | Response | ## End of HTTP response The web server outputs the HTTP response. Depending on the architecture, few things may still occur. For example, Varnish or Fastly can take specific headers into account when setting the cache or serving it. # Databases ## Using PostgreSQL Ibexa DXP uses MySQL by default, but you can also choose to install it with PostgreSQL. ### Requirements To use PostgreSQL, you need to have the `pdo_pgsql` PHP extension installed. ### Provide parameters When you run `composer install`, you're asked to [provide installation parameters](https://doc.ibexa.co/en/latest/getting_started/install_ibexa_dxp/#change-installation-parameters). > **Tip: Tip** > > It's recommended to store the database credentials in your `.env.local` file and not commit it to the Version Control System. If you use PostgreSQL, the following parameters need to be set differently in the `.env.local` file than when using MySQL: - `DATABASE_NAME` - `DATABASE_HOST` - `DATABASE_PORT` - `DATABASE_PLATFORM` must be set to `pgsql` instead of `mysql` - `DATABASE_DRIVER` must be set to `pdo_pgsql` instead of the default `pdo_mysql` - `DATABASE_VERSION` - `DATABASE_CHARSET` must be set to `utf8`, because the default value of `utf8mb4` is MySQL-specific. The rest of the installation procedure is the same as when using MySQL. # Cache Ibexa DXP offers both HTTP cache for content views, and persistence cache. - [HTTP cache](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/infrastructure_and_maintenance/cache/http_cache/http_cache/): Ibexa DXP's HTTP cache functionalities enable using reverse proxies - Symfony HttpCache Proxy, Varnish or Fastly. - [Reverse proxy](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/infrastructure_and_maintenance/cache/http_cache/reverse_proxy/): You can use Symfony HttpCache Proxy, Varnish or Fastly as reverse proxies with Ibexa DXP. - [Persistence cache](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/infrastructure_and_maintenance/cache/persistence_cache/): Persistence cache caches SPI\\Persistence calls used in common page loads. # HTTP cache Ibexa DXP provides advanced caching features needed for its own content views, to make Varnish and Fastly act as the view cache for the system. This and other features allow Ibexa DXP to be scaled up to serve high traffic websites and applications. HTTP cache is handled by the [ibexa/http-cache](https://github.com/ibexa/http-cache) bundle, which extends [friendsofsymfony/http-cache-bundle](https://foshttpcachebundle.readthedocs.io/en/latest/), a Symfony community bundle that in turn extends [Symfony HTTP cache](https://symfony.com/doc/7.4/http_cache.html). For content view responses coming from Ibexa DXP itself, this means that: - Cache is **[content-aware](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/http_cache/content_aware_cache/index.md)**, always kept up-to-date by invalidating using cache tags. - Cache is **[context-aware](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/http_cache/context_aware_cache/index.md)**, to cache request for logged-in users by varying on user permissions. All of this works across all the supported reverse proxies: - [Symfony HttpCache Proxy](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/http_cache/reverse_proxy/index.md) - limited to a single server, and with limited performance/features - [Varnish](https://varnish-cache.org/) - high performance reverse proxy - [Fastly](https://www.fastly.com/) - Varnish-based CDN service You can use all these features in custom controllers as well. # HTTP cache configuration ## Content view configuration You can configure cache globally for content views under the `ibexa.system..content` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa: system: : content: # Activates HTTP cache for content view_cache: true # Activates expiration based HTTP cache for content (very fast) ttl_cache: true # Number of seconds an HTTP response cache is valid (if ttl_cache is true, and if no custom s-maxage is set) default_ttl: 7200 ``` You may want to set a high default time to live (TTL) (`default_ttl`) to have a high cache hit ratio on your installation. As the system takes care of purges, the cache should not become stale with the exception of grace handing in Varnish and Fastly. ## Cache header rules A few redirect and error pages are served through the content view system. If you set a high `default_ttl`, they can also be served from cache. To avoid this, the installation ships with configuration to match these specific situations and set a much lower TTL. [FOSHttpCacheBundle matching rules](https://foshttpcachebundle.readthedocs.io/en/latest/reference/configuration/headers.html) enables you to specify a different TTL: ``` fos_http_cache: cache_control: rules: # Make sure cacheable (fresh) responses from Ibexa DXP which are errors/redirects get lower TTL than default_ttl - match: match_response: 'response.isFresh() && ( response.isServerError() || response.isClientError() || response.isRedirect() )' headers: overwrite: true cache_control: max_age: 5 s_maxage: 20 ``` Similarly, by default the performance tuning is applied to avoid crawlers affecting the setup too much, by caching of generic 404s and similar error pages in the following way: ``` fos_http_cache: cache_control: rules: # Example of performance tuning, force TTL on 404 pages to avoid crawlers, etc., taking too much load # Should not be set too high, as cached 404s can cause issues for future routes, URL aliases, wildcards, etc. - match: match_response: '!response.isFresh() && response.isNotFound()' headers: overwrite: true cache_control: public: true max_age: 0 s_maxage: 20 ``` ## Time-to-live value for Page blocks For the Page Builder, block cache by default respects `$content.ttl_cache$` and `$content.default_ttl$` settings. However, if the given block value has a since or till date, it's taken into account for the TTL calculation for both the block and the whole page. To overload this behavior, listen to [`BlockResponseEvents::BLOCK_RESPONSE`](https://doc.ibexa.co/en/latest/api/event_reference/page_events/index.md), and set priority to `-200` to adapt what Page field type does by default. For example, to disable cache for the block, use `$event->getResponse()->setPrivate()`. ## When to use ESI [Edge Side Includes](https://symfony.com/doc/7.4/http_cache/esi.html) (ESI) can be used to split out the different parts of a web page into separate fragments that can be freely reused as pieces by reverse proxy. In practice, with ESI, every sub-request is regenerated from application perspective. And while you can tune your system to reduce this, it always causes additional overhead in the following situations: - When cache is cold on all or some of the sub-requests - With Symfony Proxy (AppCache) there is always some overhead, even on warm cache (hits) - In development environment This may differ depending on your system, however, it's recommended to stay below 5 ESI requests per page and only using them for parts that are the same across the whole site or larger parts of it. You should not use ESI for parts that are effectively uncached, because your reverse proxy has to wait for the back end and cannot deliver cached pages directly. > **Note: ESI limitations with the URIElement SiteAccess matcher** > > It isn't possible to share ESIs across the SiteAccesses when using URI matching as URI contains the SiteAccess name encoded in its path information. # Reverse proxy ## Using Symfony reverse proxy To use the Symfony reverse proxy, you must change your `public/index.php` front controller script and wrap `Ibexa\Bundle\HttpCache\AppCache` instead of `Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache` around the kernel. ``` --- a/public/index.php +++ b/public/index.php @@ -1,9 +1,11 @@ **Caution: Caution** > > Don't enable the Symfony reverse proxy in `public/index.php` if you intend to use Varnish or Fastly. You may only use one HTTP cache at a time. ## Using Varnish or Fastly As Ibexa DXP is built on top of Symfony, it uses standard HTTP cache headers. By default, the Symfony reverse proxy is used to handle cache. You can replace it with other reverse proxies, such as Varnish, or CDN like Fastly. Using a different proxy is highly recommended as they provide better performance and more advanced features such as grace handling, configurable logic through VCL and much more. > **Note: Note** > > Use of Varnish or Fastly is a requirement for a [clustering](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/clustering/clustering/index.md) setup, as Symfony Proxy doesn't support sharing cache between several application servers. ## VCL base files For reverse proxies to work properly with your installation, you need to add the corresponding VCL files for your HTTP Cache. - Varnish config can be found in `vendor/ibexa/http-cache/docs/varnish/vcl`: - use [parameters.vcl](https://github.com/ibexa/http-cache/blob/v5.0.7/docs/varnish/vcl/parameters.vcl) for installation specific settings - plus one of the `varnish*.vcl` corresponding to your Varnish version - For example, [varnish7.vcl](https://github.com/ibexa/http-cache/blob/v5.0.7/docs/varnish/vcl/varnish7.vcl) when using Varnish 7 - Fastly config can be found in `vendor/ibexa/fastly/fastly`. You must install the following to use Fastly: - `ibexa_main.vcl` as the **main** custom VCL - `ibexa_user_hash.vcl` as another custom VCL - `snippet_re_enable_shielding.vcl` as snippet The provided `.vcl` files work both with [Fastly Shielding](https://www.fastly.com/documentation/guides/getting-started/hosts/shielding) enabled and without it. If you decide to use Fastly VCL, consider using [Fastly CLI](https://www.fastly.com/documentation/reference/tools/cli/#installing) with it to manage VCL files from the command line. To learn more, see [Prepare to use Fastly locally](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/http_cache/fastly/#prepare-for-using-fastly-locally) and [Introduction to Fastly CLI](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/http_cache/fastly/#quick-introduction-to-fastly-cli). > **Tip: Support for Fastly Shielding was added in Ibexa DXP v3.3.24 and v4.1.6** > > When you extend [FOSHttpCacheBundle](https://foshttpcachebundle.readthedocs.io/en/latest/), you can also adapt your VCL further with [FOSHttpCache documentation](https://foshttpcache.readthedocs.io/en/latest/varnish-configuration.html) to use additional features. ## Configure Varnish and Fastly The configuration of Ibexa DXP for using Varnish or Fastly requires a few steps, starting with configuring proxy. Failing to configure reverse proxies correctly may introduce several problems, including, but not limited to: - Ibexa DXP generating links with a wrong protocol schema (HTTP instead of HTTPS) if HTTPS termination is done before the web server due to the `X-Forward-Proto` headers being ignored - Ibexa DXP generating links with wrong port numbers due to the `X-Forward-Port` headers being ignored - back office showing the login screen because JWT tokens aren't accepted due to the `X-Forward-For` headers being ignored ### Configure Symfony front controller You need to consider your `TrustedProxy` configuration when you use Symfony [behind a load balancer or a reverse proxy](https://symfony.com/doc/7.4/deployment/proxies.html). To configure trusted proxies, use [Symfony semantic configuration](https://symfony.com/doc/7.4/deployment/proxies.html#solution-settrustedproxies) under the `framework.trusted_proxies` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files), for example: ``` framework: trusted_proxies: '192.0.0.1,10.0.0.0/8' ``` > **Caution: Careful when trusting dynamic IP that usesREMOTE_ADDRvalue or similar** > > On Upsun, Varnish doesn't have a static IP, like with [AWS LB](https://symfony.com/doc/7.4/deployment/proxies.html#but-what-if-the-ip-of-my-reverse-proxy-changes-constantly). For this reason, the `TRUSTED_PROXIES` env variable supports being set to value `REMOTE_ADDR`, which is equal to: > > ``` > Request::setTrustedProxies([$request->server->get('REMOTE_ADDR')], Request::HEADER_X_FORWARDED_ALL); > ``` > > When trusting remote IP like this, make sure your application is only accessible through Varnish. If it's accessible in other ways, this may result in trusting, for example, the IP of client browser instead, which would be a serious security issue. > > Make sure that **all** traffic always comes from the trusted proxy/load balancer, and that there is no other way to configure it. When using Fastly, you need to set `trusted_proxies` according to the [IP ranges used by Fastly](https://api.fastly.com/public-ip-list). > **Tip: Tip** > > You don't have to set `trusted_proxies` when using Fastly on Upsun. The Upsun router automatically changes the source IP of requests coming from Fastly, replacing the source IP with the actual client IP and removing any `X-FORWARD-...` header in the request before it reaches Ibexa DXP. For more information about setting these variables, see [Configuration examples](#configuration-examples). ### Update YML configuration Next, you need to tell Ibexa DXP to use an HTTP-based purge client (specifically the FosHttpCache Varnish purge client), and specify the URL that Varnish can be reached on: | Configuration | Parameter | Environment variable | Possible values | | ---------------------------------------------------------- | -------------------------- | ------------------------------------ | --------------------------------------------------------------------------------- | | `ibexa.http_cache.purge_type` | `purge_type` | `HTTPCACHE_PURGE_TYPE` | local, varnish, fastly | | `ibexa.system..http_cache.purge_servers` | `purge_server` | `HTTPCACHE_PURGE_SERVER` | Array of URLs to proxies when using Varnish or Fastly (`https://api.fastly.com`). | | `ibexa.system..http_cache.varnish_invalidate_token` | `varnish_invalidate_token` | `HTTPCACHE_VARNISH_INVALIDATE_TOKEN` | (Optional) For token-based authentication. | | `ibexa.system..http_cache.fastly.service_id` | `fastly_service_id` | `FASTLY_SERVICE_ID` | Service ID to authenticate with Fastly. | | `ibexa.system..http_cache.fastly.key` | `fastly_key` | `FASTLY_KEY` | Service key/token to authenticate with Fastly. | If you need to set multiple purge servers, configure them in the YAML configuration, instead of parameter or environment variable, as they only take single string value. Example configuration for Varnish as reverse proxy, providing that [front controller has been configured](#configure-symfony-front-controller): ``` ibexa: http_cache: purge_type: varnish system: # Assuming that my_siteaccess_group contains both your front-end and back-end SiteAccesses my_siteaccess_group: http_cache: # Fill in your Varnish server(s) address(es). purge_servers: [http://my.varnish.server:8081] ``` #### Varnish and Basic Auth If the Varnish server is protected by Basic Auth, specify the Basic Auth credentials within the `purge_servers` setting using the format: ``` http_cache: purge_servers: [http://myuser:mypasswd@my.varnish.server:8081] ``` Varnish is enabled by default when using Ibexa Cloud and the `purge_servers` setting is set automatically. To enable Basic Auth on Ibexa Cloud when using Varnish, specify the credentials using the following environment variables to make sure that Varnish is reachable: ``` env:HTTPCACHE_USERNAME=myuser env:HTTPCACHE_PASSWORD=mypasswd ``` If you want to use Basic Auth with Fastly on Ibexa Cloud, please see [Enable basic-auth on Fastly](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/http_cache/fastly/#enable-basic-auth-on-fastly). > **Note: Invalidating Varnish cache by using tokens** > > In setups where the Varnish server IP can change (for example, on Ibexa Cloud), you can use token-based cache invalidation through [`ibexa_purge_acl`](https://github.com/ibexa/http-cache/blob/main/docs/varnish/vcl/varnish5.vcl#L174). > > In such situation, use strong, secure hash and make sure to keep the token secret. ### Ensure proper Captcha behavior (Experience) (Commerce) If your installation uses Varnish and you want users to be able to configure and use Captcha in their forms, you must enable sending Captcha data as a response to an Ajax request. Otherwise, Varnish doesn't allow for the transfer of Captcha data to the form, and as a result, users see an empty image. To enable sending Captcha over Ajax, add the following configuration: ``` ibexa: system: default: form_builder: captcha: use_ajax: true ``` ### Update custom Captcha block (Experience) (Commerce) If you created a custom Captcha block for your site by overriding the default file (`vendor/gregwar/captcha-bundle/Resources/views/captcha.html.twig`), you must make the following changes to the custom block template file: - change the name of the block to `ajax_captcha_widget` - include the JavaScript file: ``` {{ encore_entry_script_tags('ibexa-form-builder-ajax-captcha-js', null, 'ibexa') }} ``` - add a data attribute with a `fieldId` value: ``` data-field-id="{{ field.id }}" ``` As a result, your file should be similar to [this example](https://github.com/ibexa/form-builder/blob/main/src/bundle/Resources/views/themes/standard/fields/captcha.html.twig). For more information about configuring Captcha fields, see [Captcha field](https://doc.ibexa.co/en/latest/content_management/forms/work_with_forms/#captcha-field). ### Use Fastly as HttpCache proxy [Fastly](https://www.fastly.com/) delivers Varnish as a CDN service and is supported with Ibexa DXP. To learn how it works, see [Fastly documentation](https://www.fastly.com/documentation/guides/getting-started/concepts/using-fastlys-global-pop-network). #### Configure Fastly in YML ``` ibexa: http_cache: purge_type: fastly system: # Assuming that my_siteaccess_group contains both your front-end and back-end SiteAccesses my_siteaccess_group: http_cache: purge_servers: [https://api.fastly.com] fastly: # See below for obtaining these values service_id: "ID" key: "token" ``` #### Configure Fastly using environment variables See the example below to configure Fastly with the `.env` file: ``` HTTPCACHE_PURGE_TYPE="fastly" # Optional HTTPCACHE_PURGE_SERVER="https://api.fastly.com" # See below for obtaining service ID and application key/token FASTLY_SERVICE_ID="ID" FASTLY_KEY="token" ``` #### Configure Fastly on Ibexa Cloud If you use Upsun, it's recommended to configure all environment variables through [Upsun variables](https://fixed.docs.upsun.com/guides/ibexa/fastly.html). In Ibexa DXP, Varnish is enabled by default. To use Fastly, first you must [disable Varnish](https://fixed.docs.upsun.com/guides/ibexa/fastly.html#remove-varnish-configuration). #### Get Fastly service ID and API token To get the service ID, log in to . In the upper menu, click the **CONFIGURE** tab. The service ID is displayed next to the name of your service on any page. For instructions on how to generate a Fastly API token, see [the Fastly guide](https://www.fastly.com/documentation/guides/account-info/account-management/using-api-tokens). The API token needs the `purge_all` an `purge_select` scopes. ### Configuration examples See below the most common configuration examples for the system, using environment variables. Example for Varnish with the `.env` file: ``` HTTPCACHE_PURGE_TYPE="varnish" HTTPCACHE_PURGE_SERVER="http://varnish:80" ``` Example for Apache with `mod_env`: ``` SetEnv HTTPCACHE_PURGE_TYPE varnish SetEnv HTTPCACHE_PURGE_SERVER "http://varnish:80" ``` Example for Nginx: ``` fastcgi_param HTTPCACHE_PURGE_TYPE varnish; fastcgi_param HTTPCACHE_PURGE_SERVER "http://varnish:80"; ``` Example for Upsun: You can configure environment variables through [Upsun variables](https://fixed.docs.upsun.com/guides/ibexa/fastly.html). > **Tip: Tip** > > For HTTP cache, you most likely only use this for configuring Fastly for production and optionally staging, allowing `variables:env:` in `.platform.app.yaml` to, for example, specify Varnish or Symfony proxy as default for dev environment. #### Apache with Varnish ``` # mysite_com.conf # Configure Varnish SetEnv HTTPCACHE_PURGE_TYPE varnish SetEnv HTTPCACHE_PURGE_SERVER "http://varnish:80" # Configure IP of your Varnish server to be trusted proxy # !! Replace IP with the real one used by Varnish SetEnv TRUSTED_PROXIES "193.22.44.22" ``` #### Nginx with Fastly ``` # mysite_com.conf # Configure Fastly fastcgi_param HTTPCACHE_PURGE_TYPE fastly; fastcgi_param HTTPCACHE_PURGE_SERVER "https://api.fastly.com"; # See above for obtaining service ID and application key/token fastcgi_param FASTLY_SERVICE_ID "ID" fastcgi_param FASTLY_KEY "token" ``` ## Stale cache Stale cache, or grace mode in Varnish, occurs when: - Cache is served some time after the TTL expired. - When the back-end server doesn't respond. This has several benefits for high traffic installations to reduce load to the back end. Instead of creating several concurrent requests for the same page to the back end, the following happens when a page has been soft purged: - Next request hitting the cache triggers an asynchronous lookup to the back end. - If cache is still within grace period, first and subsequent requests for the content are served from cache, and don't wait for the asynchronous lookup to finish. - The back-end lookup finishes and refreshes the cache so any subsequent requests get a fresh cache. By default, Ibexa DXP always soft purges content on reverse proxies that support it (Varnish and Fastly), with the following logic in the out-of-the-box VCL: - Cache is within grace period. - Either the server isn't responding, or the request comes without a session cookie (anonymous user). Serving grace isn't always allowed by default because: - It's a safe default. Even if for anonymous users, stale cache can be confusing during acceptance testing. - It means REST API, which is used by the back office, would serve stale data, breaking the UI. > **Tip: Customizing stale cache handling** > > If you want to use grace handling for logged-in users as well, you can adapt the provided VCL to add a condition for opting out if the request has a cookie and the path contains REST API prefix to make sure the back office isn't negatively affected. > > If you want to disable grace mode, you can adapt the VCL to do hard instead of soft purges, or set grace/stale time to `0s`. # Context-aware HTTP cache Ibexa DXP allows caching requests made by logged-in users. This is called (user) context-aware cache. It means that HTTP cache is unique per set of user permissions (roles and limitations), and there are variations of cache shared only among users that have the exact same permissions. So if a user browses a list of children locations, they only see children locations they have access to, even if their rendering is served from HTTP cache. This is accomplished by varying on a header called `X-User-Context-Hash`, which the system populates on the request. The [logic for this](#request-lifecycle) is accomplished in the provided VCL for Varnish and Fastly. A similar but internal logic is done in the provided enhanced Symfony Proxy (AppCache). ## Request lifecycle This expands steps covered in [FOSHttpCacheBundle documentation on user context feature](https://foshttpcachebundle.readthedocs.io/en/latest/features/user-context.html#how-it-works): 1. A client (browser) requests URI `/foo`. 2. The caching proxy receives the request and holds it. It first sends a hash request to the application's context hash route: `/_fos_user_context_hash`. 3. The application receives the hash request. An event subscriber (`UserContextSubscriber`) aborts the request immediately after the Symfony firewall is applied. The application calculates the hash (`HashGenerator`) and then sends a response with the hash in a custom header (`X-User-Context-Hash`). 4. The caching proxy receives the hash response, copies the hash header to the client's original request for `/foo` and restarts the modified original request. 5. If the response to `/foo` should differ per user context, the application sets a `Vary: X-User-Context-Hash` header, which makes Proxy store the variations of this cache varying on the hash value. The next time a request comes in from the same user, application lookup for the hash (step 3) doesn't take place, as the hash lookup itself is cached by the cache proxy as described below. ### User context hash caching Example of a response sent to reverse proxy from `/_fos_user_context_hash` with [Ibexa DXP's default config](#default-options-for-foshttpcachebundle): ``` HTTP/1.1 200 OK X-User-Context-Hash: Content-Type: application/vnd.fos.user-context-hash Cache-Control: public, max-age=600 Vary: Cookie, Authorization ``` In the example above the response is set to be cached for 10 minutes. It varies on the `Cookie` header to be able to cache it for the given user. To optimize it, the default VCL strips any cookie other than session cookies to make this work. It also varies on `Authorization` to cover any possible basic authorization headers in case that is used over sessions for some requests. > **Note: Problems with stale user hash** > > If you notice issues with stale hash usage, before you disable this cache, make sure login or logout always generates new session IDs and performs a full redirect to make sure no requests are being made with stale user context hashes. > **Caution: Limitations of the user context hash** > > If you use URI-based SiteAccess matching on a multi-repository installation (multiple databases), the default SiteAccess on the domain needs to point to the same repository (database), because `/_fos_user_context_hash` isn't SiteAccess-aware by default (see `ibexa.rest.default_router.non_siteaccess_aware_routes` parameter). This occurs because reverse proxy doesn't have knowledge about SiteAccesses and it doesn't pass the whole URL to be able to cache the user context hash response. > > The only known workaround is to make it SiteAccess aware, and have custom VCL logic tied to your SiteAccess matching with Varnish/Fastly, to send the SiteAccess prefix as URI. > **Caution: Default options for FOSHttpCacheBundle** > > The following configuration is defined by default for FOSHttpCacheBundle. You should not override these settings unless you know what you're doing. > > ``` > fos_http_cache: > proxy_client: > default: varnish > varnish: > http: > servers: ['$http_cache.purge_servers$'] > tag_mode: 'purgekeys' > > user_context: > enabled: true > hash_cache_ttl: 600 > # NOTE: These are also defined/used in AppCache, in Varnish VCL, and Fastly VCL > session_name_prefix: IBX_SESSION_ID > ``` ## Personalize responses Here are some generic recommendations on how to approach personalized content with Ibexa DXP / Symfony: 1. ESI with vary by cookie: Default VCL strips everything except session cookie, so this is effectively "per user". If you're on single-server setup without Varnish or Fastly, you can use the same cookie logic on the web server instead. This a low effort solution, and can be enough for one fragment that is reused across the whole site, for example, in header to show user name: Example: ``` // Inside a custom controller action, or even a Content View controller $response->setVary('Cookie'); ``` 2. Ajax/JS lookup to "uncached" custom Symfony controllers: This method doesn't consume memory in Varnish. It can optionally be cached with custom logic: Symfony Cache on server side and/or with client side caching techniques. This should be done as Ajax/JS lookup to avoid the uncached request that slows down the whole delivery of Vanish if it's done as ESI. This solution requires more effort depending on project requirements (for example, traffic load). 3. Custom vary by logic, for example, `X-User-Preference-Hash` inspired by `X-User-Context-Hash`: This method allows for fine-grained caching as you can explicitly vary on this in only the places that need it. This solution requires more effort (controller, VCL logic and adapting your own code), see the examples below. > **Tip: Dealing with paywall use cases** > > If you need to handle a paywall on a per-item basis, or example, do a lookup to backend for each URL where this is relevant. > > You can find an example for paywall authorization in [FOSHTTPCache documentation](https://foshttpcache.readthedocs.io/en/latest/user-context.html#alternative-for-paywalls-authorization-request). ### Best practices for custom vary by logic For information on how user context hashes are generated, see [FOSHttpCacheBundle documentation](https://foshttpcachebundle.readthedocs.io/en/latest/features/user-context.html#generating-hashes). Ibexa DXP implements a custom context provider to make user context hash reflect the current user's roles and limitations. This is needed given Ibexa DXP's more complex permission model compared to Symfony's. You can technically extend the user context hash by [implementing your own custom context provider(s)](https://foshttpcachebundle.readthedocs.io/en/latest/reference/configuration/user-context.html#custom-context-providers). However, **this is strongly discouraged** as it means increasing the amount of cache variations stored in proxy for every single cache item, lowering cache hit ratio and increasing memory use. Instead, you can create your own hash header for use cases where you need it. This way only controllers and views that really vary by your custom logic varies on it. You can use several methods to do it, ranging from completely custom VCL logic and dedicated controller to respond with hash to trusted proxy lookups, but this means additional lookups. ### Example for custom vary by logic You can extend `/_fos_user_context_hash` lookup to add another HTTP header with custom hash for your needs, and adapt the user context hash VCL logic to use the additional header. To avoid overloading any application code, take advantage of Symfony's event system: 1. Add a [Response event (`kernel.response`)](https://symfony.com/doc/7.4/reference/events.html#kernel-response) [listener or subscriber](https://symfony.com/doc/7.4/event_dispatcher.html) to add your own hash to `/_fos_user_context_hash`: ``` public function addPreferenceHash(FilterResponseEvent $event) { $response = $event->getResponse(); if ($response->headers->get('Content-Type') !== 'application/vnd.fos.user-context-hash') { return; } $response->headers->set('X-User-Preference-Hash', ''); } ``` 2. Adapt VCL logic to pass the header to requests: ``` @@ -174,6 +174,7 @@ sub ibexa_user_context_hash { if (req.restarts == 0 && (req.http.accept ~ "application/vnd.fos.user-context-hash" || req.http.x-user-context-hash + || req.http.x-user-preference-hash ) ) { return (synth(400, "Bad Request")); @@ -263,12 +264,19 @@ sub vcl_deliver { && resp.http.content-type ~ "application/vnd.fos.user-context-hash" ) { set req.http.x-user-context-hash = resp.http.x-user-context-hash; + set req.http.x-user-preference-hash = resp.http.x-user-preference-hash; return (restart); } // If we get here, this is a real response that gets sent to the client. + // Remove the vary on user preference hash, no need to expose this publicly. + if (resp.http.Vary ~ "X-User-Preference-Hash") { + set resp.http.Vary = regsub(resp.http.Vary, "(?i),? *X-User-Preference-Hash *", ""); + set resp.http.Vary = regsub(resp.http.Vary, "^, *", ""); + } + // Remove the vary on user context hash, this is nothing public. Keep all // other vary headers. if (resp.http.Vary ~ "X-User-Context-Hash") { ``` 3. Add `Vary` in your custom controller or content view controller: ``` $response->setVary('X-User-Preference-Hash'); // If you _also_ need to vary on Ibexa DXP permissions, instead use: //$response->setVary(['X-User-Context-Hash', 'X-User-Preference-Hash']); ``` # Content-aware HTTP cache HTTP cache in Ibexa DXP is aware of which content or entity it's connected to. This awareness is accomplished by means of cache tagging. All supported reverse proxies are content-aware. > **Note: Tag header is stripped in production for security reasons** > > For security reasons this header, and other internal cache headers, are stripped from output in production by the reverse proxy (in VCL for Varnish and Fastly). ## Cache tags Understanding tags is the key to making the most of Ibexa DXP's HTTP cache. Tags form a secondary set of keys assigned to every cache item, on top of the "primary key" which is the URI. Like an index in a database, a tag is typically used for anything relevant that represents the given cache item. Tags are used for cache invalidation. For example, the system tags every article response, and when the article content type is updated, it tells Varnish that all articles should be considered stale and updated in the background when someone requests them. Current content tags (and when the system purges on them): - Content: `c` - Purged on all smaller or larger changes to content (including its metadata, fields and locations). - Content version: `cv` - Purged when any version of Content is changed (for example, a draft is created or removed). - Content type: `ct` - Used when the content type changes, affecting content of its type. - Location: `l` - Used for clearing all cache relevant for a given location. - Parent Location: `pl<[parent-]location-id>` - Used for clearing all children of a location (`pl`), or all siblings (`pl`). - Path: `p` - For operations that change the tree itself, for example, move or remove. - Relation: `r` - Only purged on when content updates are severe enough to also affect reverse relations. - Relation location: `rl` - Same as relation, but by location ID. > **Note: Automatic repository prefixing of cache tags** > > As Ibexa DXP supports multi-repository (multi-database) setups that can have overlapping IDs, the shared HTTP cache systems need to distinguish tags relevant to the different content repositories. > > This is why in multi-repository setup you can see cache tags such as `1p2`. In this example `1` represents the index among configured repositories, meaning the second repository in the system. > > Tags aren't prefixed for default repository (index "0"). The content tags are returned in a header in the responses from Ibexa DXP. The header name is dependent on which HTTP Cache Ibexa DXP is configured with: - Symfony reverse proxy: `X-Cache-Tags` - Varnish: `xkey` - Fastly: `Surrogate-Key` Examples: - `X-Cache-Tags: ez-all,c52,ct42,l2,pl1,p1,p2,r56,r57` - `xkey: ez-all c52 ct42 l2 pl1 p1 p2 r56 r57` - `Surrogate-Key: ez-all c52 ct42 l2 pl1 p1 p2 r56 r57` ### Troubleshooting - Cache header too long errors In case of complex content, for example, Pages with many blocks, or RichText with a lot of embeds/links, you can encounter problems with too long cache header on responses. It happens because necessary cache entries may not be tagged properly. You may also see `502 Headers too long` errors, and webserver refusing to serve the page. You can solve this issue in one of the following ways: #### A. Allow larger headers Varnish configuration: - [http_resp_hdr_len](https://varnish-cache.org/docs/6.0/reference/varnishd.html#http-resp-hdr-len) (default 8k, change to for example, 32k) - [http_max_hdr](https://varnish-cache.org/docs/6.0/reference/varnishd.html#http-max-hdr) (default 64, change to for example, 128) - [http_resp_size](https://varnish-cache.org/docs/6.0/reference/varnishd.html#http-resp-size) (default 23k, change to for example, 96k) - [workspace_backend](https://varnish-cache.org/docs/6.0/reference/varnishd.html#workspace-backend) (default 64k, change to for example, 128k) If you need to see these long headers in `varnishlog`, adapt the [vsl_reclen](https://varnish-cache.org/docs/6.0/reference/varnishd.html#vsl-reclen) setting. Nginx has a default limit of 4k/8k when buffering responses: - For [PHP-FPM](https://www.php.net/manual/en/install.fpm.php) setup using proxy module, configure [proxy_buffer_size](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffer_size) - For FastCGI setup using fastcgi module, configure [fastcgi_buffer_size](https://nginx.org/en/docs/http/ngx_http_fastcgi_module.html#fastcgi_buffer_size) Fastly has a `Surrogate-Key` header limit of 16 kB, and this cannot be changed. Apache has a [hard](https://github.com/apache/httpd/blob/5f32ea94af5f1e7ea68d6fca58f0ac2478cc18c5/server/util_script.c#L495) [coded](https://github.com/apache/httpd/blob/7e2d26eac309b2d79e467ef586526c10e0f226f8/include/httpd.h#L299-L303) limit of 8 kB, so if you face this issue consider using Nginx instead. #### B. Limit tags header output by system 1. For inline rendering displaying the content name, image attribute, and/or link, it would be enough to: - Look into how many inline (non ESI) render calls for content rendering you're doing, and see if you can organize it differently. - Consider inlining the views not used elsewhere in the given template and [tagging the response in Twig](#response-tagging-in-templates) with "relation" tags. - (Optional) You can set reduced cache TTL for the given view, to reduce the risk of stale cache on subtree operations affecting the inlined content. 2. You can opt in to set a max length parameter (in bytes) and corresponding ttl (in seconds) for cases when the limit is reached. The system logs a warning where the limit is reached, and when needed, you can optimize these cases as described above. ``` parameters: # Warning, setting this means you risk losing tag information, risking stale cache. Here set below 8k: ibexa.http_cache.tags.header_max_length: 7900 # In order to reduce risk of stale cache issues, you should set a lower TTL here then globally (here set as 2h) ibexa.http_cache.tags.header_reduced_ttl: 7200 ``` ## Response tagging with content view For content views response tagging is done automatically, and cache system outputs headers as follows: ``` HTTP/1.1 200 OK Cache-Control: public, max-age=86400 xkey: ez-all c1 ct1 l2 pl1 p1 p2 ``` If the given content has several locations, you can see several `l` and `p` tags in the response. > **Note: How response tagging for ContentView is done internally** > > In `ibexa/http-cache` there is a dedicated response listener `HttpCacheResponseSubscriber` that checks if: > > - the response has attribute `view` > - the view implements `Ibexa\Core\MVC\Symfony\View\CachableView` > - cache isn't disabled on the individual view > > If that checks out, the response is adapted with the following: > > - `ResponseCacheConfigurator` applies SiteAccess settings for enabled/disabled cache and default TTL. > - `DispatcherTagger` dispatches the built-in ResponseTaggers which generate the tags as described above. ### ResponseConfigurator A `ReponseCacheConfigurator` configures an HTTP Response object, makes the response public, adds tags, and sets the shared max age. It's provided to `ReponseTaggers` that use it to add the tags to the response. The `ConfigurableResponseCacheConfigurator` (`Ibexa\HttpCache\ResponseConfigurator\ConfigurableResponseCacheConfigurator`) follows the `view_cache` configuration and only enables cache if it's enabled in the configuration. ### Delegator and Value taggers - Delegator taggers - extract another value or several from the given value and pass it on to another tagger. For example, a `ContentView` is covered both by the `ContentValueViewTagger` and `LocationValueViewTagger`, where the first extracts the content from the `ContentView` and passes it to the `ContentInfoTagger`. - Value taggers - extract the `Location` and pass it on to the `LocationViewTagger`. ## DispatcherTagger Accepts any value and passes it on to every tagger registered with the service tag `ibexa.cache.http.response.tagger`. ## Response tagging in controllers For tagging needs in controllers, there are several options, here presented in recommended order: 1. Reusing `DispatcherTagger` to pick correct tags. Examples for tagging everything needed for content using the autowireable `ResponseTagger` interface: ``` /** @var \Ibexa\Contracts\HttpCache\ResponseTagger\ResponseTagger $responseTagger */ // If you have a View object you can simply call: $responseTagger->tag($view); // Or if you have content / Location object only, you can instead provide content info and Location: $responseTagger->tag($contentInfo); $responseTagger->tag($location); ``` 2. Use `ContentTagInterface` API for content related tags. Examples for adding specific content tags using the autowireable `ContentTagInterface`: ``` /** @var \Ibexa\Contracts\HttpCache\Handler\ContentTagInterface $tagHandler */ // Example for tagging everything needed for Content: $tagHandler->addContentTags([$content->id]); $tagHandler->addLocationTags([$location->id]); $tagHandler->addParentLocationTags([$location->parentLocationId]); $tagHandler->addPathTags($location->path); $tagHandler->addContentTypeTags([$content->getContentType()->id]); // Example when using ESI as also shown below using FOS tag handler (there is also a method for relation locations): $tagHandler->addRelationTags([33, 44]); ``` 3. Manually add tags yourself using low-level FOS `TagHandler`. In PHP, FOSHttpCache exposes the `fos_http_cache.http.symfony_response_tagger` service which enables you to add tags to a response. The following example adds minimal tags when ID 33 and 34 are rendered in ESI, but parent response needs these tags to get refreshed if they're deleted: ``` /** @var \FOS\HttpCacheBundle\Http\SymfonyResponseTagger $responseTagger */ $responseTagger->addTags([ContentTagInterface::RELATION_PREFIX . '33', ContentTagInterface::RELATION_PREFIX . '34']); ``` See [Tagging from code](https://foshttpcachebundle.readthedocs.io/en/latest/features/tagging.html#tagging-from-twig-templates) in FOSHttpCacheBundle doc. 4. Use deprecated `X-Location-Id` header. For custom or built-in controllers (for example, REST) that still use `X-Location-Id`, `XLocationIdResponseSubscriber` handles translating this header to tags. It supports singular and comma-separated location ID value(s): ``` /** @var \Symfony\Component\HttpFoundation\Response $response */ $response->headers->set('X-Location-Id', 123); // Alternatively using several Location ID values $response->headers->set('X-Location-Id', '123,212,42'); ``` > **Caution: X-Location-Id use is deprecated** > > `X-Location-Id` is deprecated and removed in future. For rendering content it's advised to refactor to use content view, if not applicable `ContentTagInterface` or lastly manually output tags. ## Response tagging in templates 1. `ibexa_http_cache_tag_location()` For full content tagging when inline rendering, use the following: ``` {{ ibexa_http_cache_tag_location(location) }} ``` 2. `ibexa_http_cache_tag_relation_ids()` or `ibexa_http_cache_tag_relation_location_ids()` When you want to reduce the amount of tags, or the inline content is rendered using ESI, a minimum set of tags can be set: ``` {{ ibexa_http_cache_tag_relation_ids(content.id) }} {# Or using array for several values #} {{ ibexa_http_cache_tag_relation_location_ids([field1.value.destinationContentId, field2.value.destinationContentId]) }} ``` 3. `{{ fos_httpcache_tag(['r33', 'r44']) }}` As a last resort you can also use the following function from FOS which lets you set low level tags directly: ``` {{ fos_httpcache_tag('r33') }} {# Or using array for several values #} {{ fos_httpcache_tag(['r33', 'r44']) }} ``` See [Tagging from Twig Templates](https://foshttpcachebundle.readthedocs.io/en/latest/features/tagging.html#tagging-from-twig-templates) in FOSHttpCacheBundle documentation. ## Tag purging ### Default tag purging `ibexa/http-cache` uses repository API event subscribers to listen to events emitted on repository operations, and depending on the operation triggers expiry on a specific tag or set of tags. All event subscribers can be found in `http-cache/src/lib/EventSubscriber/CachePurge`. ### Tags purged on publish event Below is an example of a content structure. The tags which the content view controller adds to each location are also listed: ``` - [Home] (content-id=52, location-id=2) ez-all c52 ct42 l2 pl1 p1 p2 | - [Parent1](content-id=53, location-id=20) ez-all c53 ct1 l20 pl2 p1 p2 p20 | - [Child](content-id=55, location-id=22) ez-all c55 ct1 l22 pl20 p1 p2 p20 p22 - [Parent2](content-id=54, location-id=21) ez-all c55 ct1 l22 pl2 p1 p2 p22 ``` In the event when a new version of `Child` is published, the following keys are purged: - `c55`, because Content `[Child]` was changed - `r55`, because cache for any object that has a relation to Content `[Child]` should be purged - `l22`, because location `[Child]` has changed ( that would be location holding content-id=55) - `pl22`, because cache for children of `[Child]` should be purged - `rl22`, because cache for any object that has a relation to Location `[Child]` should be purged - `l20`, because cache for parent of `[Child]` should be purged - `pl20`, because cache for siblings of `[Child]` should be purged In summary, HTTP Cache for any location representing `[Child]`, any Content that relates to the Content `[Child]`, the location for `[Child]`, any children of `[Child]`, any location that relates to the location `[Child]`, location for `[Parent1]`, any children on `[Parent1]`. Effectively, in this example HTTP cache for `[Parent1]` and `[Child]` is cleared. ### Tags purged on move event With the same content structure as above, the `[Child]` location is moved below `[Parent2]`. The new structure is then: ``` - [Home] (content-id=52, location-id=2) ez-all c52 ct42 l2 pl1 p1 p2 | - [Parent1](content-id=53, location-id=20) ez-all c53 ct1 l20 pl2 p1 p2 p20 - [Parent2](content-id=54, location-id=21) ez-all c55 ct1 l22 pl2 p1 p2 p22 | - [Child](content-id=55, location-id=22) ez-all c55 ct1 l22 pl21 p1 p2 p21 p22 ``` The following keys are purged during the move: - `l20`, because cache for previous parent of `[Child]` should be purged (`[Parent1]`) - `pl20`, because cache for children of `[Parent1]` should be purged - `l21`, because cache for new parent of `[Child]` should be purged (`[Parent2]`) - `pl21`, because cache for all children of new parent (`[Parent2]`) should be purged - `p22`, because cache for any element below `[Child]` should be purged (because path has changed) In other words, HTTP Cache for `[Parent1]`, children of `[Parent1]` ( if any ), `[Parent2]`, children of `[Parent2]` ( if any ), `[Child]` and any subtree below `[Child]`. ### Custom purging from code While the system purges tags whenever API is used to change data, you may need to purge directly from code. For that you can use the built-in purge client: ``` /** @var \Ibexa\Contracts\HttpCache\PurgeClient\PurgeClientInterface $purgeClient */ // Example for purging by Location ID: $purgeClient->purge([ContentTagInterface::LOCATION_PREFIX . $location->id]); // Example for purging all cache for instance for full re-deploy cases, usually this triggers an expiry (soft purge): $purgeClient->purgeAll(); ``` ### Purging from command line Example for purging by location and by content ID: ``` bin/console fos:httpcache:invalidate:tag l44 c33 ``` Example for purging by all cache: ``` bin/console fos:httpcache:invalidate:tag ez-all ``` > **Tip: Purge is done on the current repository** > > Similarly to purging from code, the tags you purge on, are prefixed to match the currently configured SiteAccess. When you use this command in combination with multi-repository setup, make sure to specify SiteAccess argument. ## Testing and debugging HTTP cache It's important to test your code in an environment which is as similar as your production environment as possible. That means that if only are testing locally using the default Symfony Reverse proxy when your are going to use Varnish or Fastly in production, you're likely ending up some (bad) surprises. Due to the Symfony reverse proxy's lack of support for ESIs, it behaves quite different from Varnish and Fastly in some aspects. If you're going to use Varnish in production, make sure you also test your code with Varnish. If you're going to use Fastly in production, testing with Fastly in your developer install is likely not feasible (you're local development environment must then be accessible for Fastly). Testing with Varnish instead in most cases does the job. But if you need to change the varnish configuration to make your site work, be aware that Varnish and Fastly uses different dialects, and that .vcl code for Varnish V6.x doesn't likely work as-is on Fastly. This section describes to how to debug problems related to HTTP cache. ``` You must be able to look both at responses and headers Ibexa DXP sends to HTTP cache, and not so much at responses and headers the HTTP cache sends to the client (web browser). It means you must be able to send requests to your origin (web server) that don't go through Varnish or Fastly. If you run Nginx and Varnish on premise, you should know what host and port number both Varnish and Nginx runs on. ``` If you perform tests on Fastly enabled environment on Ibexa Cloud provided by Upsun, you need to use the Upsun dashboard to obtain the endpoint for Nginx. The following example shows how to debug and check why Fastly doesn't cache the front page properly. If you run the command multiple times: `curl -IXGET https://www.staging.foobar.com.us-2.platformsh.site` it always outputs: ``` HTTP/2 200 (...) x-cache: MISS ``` ### Nginx endpoint on Ibexa Cloud #### Finding Nginx endpoint for environments located on the grid To find the Nginx point, first, you need to know in which region your project is located. To do that, go to the Ibexa Cloud dashboard. To find a valid route, click an element in the **URLs** drop-down for the specified environment and select the route. A route may look like this: `https://www.staging.foobar.com.us-2.platformsh.site/` In this case the region is `us-2` and you can find the public IP list on [Upsun documentation page](https://fixed.docs.upsun.com/development/regions.html#public-ip-addresses). Typically, you can add a `gw` to the hostname and use nslookup to find it. ``` $ nslookup > gw.us-2.platformsh.site (...) Address: 1.2.3.4 ``` You can also use the [Ibexa Cloud CLI](https://cli.ibexa.co/) (which has the same command as the Upsun CLI) to find [the endpoint](https://fixed.docs.upsun.com/domains/steps/dns.html): ``` ibexa_cloud environment:info edge_hostname ``` #### Finding Nginx endpoint on dedicated cloud If you have a dedicated 3-node cluster on Upsun, the procedure for getting the endpoint to environments that are located on that cluster (`production` and sometimes also `staging`) is slightly different. In the **URLs** drop-down in the Ibexa Cloud dashboard, find the route that has the format `somecontent.[clusterid].ent.platform.sh/`, for example, `myenvironment.abcdfg2323.ent.platform.sh/` The endpoint in case has the format `c.[clusterid].ent.platform.sh`, for example, `c.asddfs2323.ent.platform.sh/`. Next, use nslookup to find the IP: ``` $ nslookup > c.asddfs2323.ent.platform.sh (...) Address: 1.2.3.4 ``` ### Fetching user context hash As explained in [User Context Hash caching](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/http_cache/context_aware_cache/#user-context-hash-caching), the HTTP cache indexes the cache based on the user-context-hash. Users with the same user-context-hash share the same cache (as long as Ibexa DXP responds with `Vary: X-User-Context-Hash`). To simulate the requests the HTTP cache sends to Ibexa DXP, you need this user-context-hash. To obtain it, use `curl`. ``` $ curl -IXGET --resolve www.staging.foobar.com.us-2.platformsh.site:443:1.2.3.4 --header "Surrogate-Capability: abc=ESI/1.0" --header "accept: application/vnd.fos.user-context-hash" --header "x-fos-original-url: /" https://www.staging.foobar.com.us-2.platformsh.site/_fos_user_context_hash ``` Some notes about each of these parameters: - `-IXGET`, one of many ways to tell curl that we want to send a GET request, but we are only interested in outputting the headers - `--resolve www.staging.foobar.com.us-2.platformsh.site:443:1.2.3.4` - We tell curl not to do a DNS lookup for `www.staging.foobar.com.us-2.platformsh.site`. We do that because in our case that resolves to the Fastly endpoint, not our origin (nginx) - We specify `443` because we are using `https` - We provide the IP of the nginx endpoint at Upsun (`1.2.3.4` in this example) - `--header "Surrogate-Capability: abc=ESI/1.0"`, strictly speaking not needed when fetching the user-context-hash, but this tells Ibexa DXP that client understands ESI tags. It's good practice to always include this header when imitating the HTTP Cache. - `--header "accept: application/vnd.fos.user-context-hash"` tells Ibexa DXP that the client wants to receive the user-context-hash - `--header "x-fos-original-url: /"` is required by the fos-http-cache bundle to deliver the user-context-hash - `https://www.staging.foobar.com.us-2.platformsh.site/_fos_user_context_hash` : here we use the hostname we earlier told curl how to resolve using `---resolve`. `/_fos_user_context_hash` is the route to the controller that are able to deliver the user-context-hash. - You may also provide the session cookie (\`--cookie ".....=....") for a logged-in-user if you're interested in the x-user-context-hash for a different user but anonymous The output for this command should look similar to this: ``` HTTP/1.1 200 OK Server: nginx/1.27.0 Content-Type: application/vnd.fos.user-context-hash Transfer-Encoding: chunked Connection: keep-alive X-User-Context-Hash: daea248406c0043e62997b37292bf93a8c91434e8661484983408897acd93814 Cache-Control: max-age=600, public Date: Tue, 31 Aug 2021 13:35:00 GMT Vary: Origin Vary: cookie Vary: authorization X-Cache-Debug: 1 Surrogate-Key: ez-user-context-hash ez-all fos_http_cache_hashlookup- ``` The header `X-User-Context-Hash` is the one of the interest here, but you may also note the `Surrogate-Key` which holds the [cache tags](#cache-tags). ### Fetching HTML response Now you have the user-context-hash, and you can ask origin for the actual resource you're after: ``` $ curl -IXGET --resolve www.staging.foobar.com.us-2.platformsh.site:443:1.2.3.4 --header "Surrogate-Capability: abc=ESI/1.0" --header "x-user-context-hash: daea248406c0043e62997b37292bf93a8c91434e8661484983408897acd93814" https://www.staging.foobar.com.us-2.platformsh.site/ ``` The output : ``` HTTP/1.1 200 OK Server: nginx/1.27.0 Content-Type: text/html; charset=UTF-8 Transfer-Encoding: chunked Connection: keep-alive Cache-Control: public, s-maxage=86400 Date: Wed, 01 Sep 2021 07:18:27 GMT X-Cache-Debug: 1 Vary: X-User-Context-Hash Vary: X-Editorial-Mode Surrogate-Control: content="ESI/1.0" Surrogate-Key: ez-all c52 ct42 l2 pl1 p1 p2 r56 r57 ``` The `Cache-Control` header tells the HTTP cache to store the result in the cache for 1 day (86400 seconds) The `Vary: X-User-Content-Hash` header tells the HTTP cache that this cache element may be used for all users which has the given `x-user-hash` (`daea248406c0043e62997b37292bf93a8c91434e8661484983408897acd93814`). The document might also be removed from the cache by purging any of the keys provided in the `Surrogate-Key` header. So back to the original problem here. This resource is for some reason not cached by Fastly (remember the `x-cache: MISS` we started with). But origin says this page can be cached for 1 day. How can that be? The likely reason is that this page also contains some ESI fragments and that one or more of these aren't cacheable. So, first let's see if there are any ESIs here. We remove the `-IXGET` options (to see content of the response, not only headers) to curl and search for esi: ``` $ curl --resolve www.staging.foobar.com.us-2.platformsh.site:443:1.2.3.4 --header "Surrogate-Capability: abc=ESI/1.0" --header "x-user-context-hash: daea248406c0043e62997b37292bf93a8c91434e8661484983408897acd93814" https://www.staging.foobar.com.us-2.platformsh.site/ | grep esi ``` The output is: ``` ``` Now, investigate the response of each of these ESI fragments to understand what is going on. It's important to put that URL in single quotes as the URLS to the ESIs include special characters that can be interpreted by the shell. #### 1st ESI ``` $ curl -IXGET --resolve www.staging.foobar.com.us-2.platformsh.site:443:1.2.3.4 --header "Surrogate-Capability: abc=ESI/1.0" --header "x-user-context-hash: daea248406c0043e62997b37292bf93a8c91434e8661484983408897acd93814" 'https://www.staging.foobar.com.us-2.platformsh.site/_fragment?_hash=B%2BLUWB2kxTCc6nc5aEEn0eEqBSFar%2Br6jNm8fvSKdWU%3D&_path=locationId%3D2%26contentId%3D52%26blockId%3D11%26versionNo%3D3%26languageCode%3Deng-GB%26serialized_siteaccess%3D%257B%2522name%2522%253A%2522site%2522%252C%2522matchingType%2522%253A%2522default%2522%252C%2522matcher%2522%253Anull%252C%2522provider%2522%253Anull%257D%26serialized_siteaccess_matcher%3Dnull%26_format%3Dhtml%26_locale%3Den_GB%26_controller%3DEzSystems%255CEzPlatformPageFieldTypeBundle%255CController%255CBlockController%253A%253ArenderAction' ``` This ESI is handled by a controller in the `FieldTypePage` bundle provided by Ibexa DXP. The output is: ``` HTTP/1.1 200 OK Server: nginx/1.27.0 Content-Type: text/html; charset=UTF-8 Transfer-Encoding: chunked Connection: keep-alive Cache-Control: public, s-maxage=86400 Date: Wed, 01 Sep 2021 07:51:40 GMT Vary: Origin Vary: X-User-Context-Hash Vary: X-Editorial-Mode X-Cache-Debug: 1 Surrogate-Key: ez-all c52 l2 ``` The headers here look correct and don't indicate that this ESI isn't cached by the HTTP cache. The second ESI has a similar response. #### 3rd ESI ``` $ curl -IXGET --resolve www.staging.foobar.com.us-2.platformsh.site:443:1.2.3.4 --header "Surrogate-Capability: abc=ESI/1.0" --header "x-user-context-hash: daea248406c0043e62997b37292bf93a8c91434e8661484983408897acd93814" 'https://www.staging.foobar.com.us-2.platformsh.site//_fragment?_hash=lnKTnmv6bb1XpaMPWRjV3sNazbn9rDXskhjGae1BDw8%3D&_path=locationId%3D2%26contentId%3D52%26blockId%3D13%26versionNo%3D3%26languageCode%3Deng-GB%26serialized_siteaccess%3D%257B%2522name%2522%253A%2522site%2522%252C%2522matchingType%2522%253A%2522default%2522%252C%2522matcher%2522%253Anull%252C%2522provider%2522%253Anull%257D%26serialized_siteaccess_matcher%3Dnull%26_format%3Dhtml%26_locale%3Den_GB%26_controller%3DEzSystems%255CCustomBundle%255CController%255CFooController%253A%253AcustomAction' ``` This ESI is handled by a custom `FooController::customAction` and the output of the command is: Output: ``` HTTP/1.1 200 OK Server: nginx/1.27.0 Content-Type: text/html; charset=UTF-8 Transfer-Encoding: chunked Connection: keep-alive Set-Cookie: IBX_SESSION_ID21232f297a57a5a743894a0e4a801fc3=asrpqgmh5ll5ssseca3cov8er7; path=/; HttpOnly; SameSite=lax Cache-Control: public, s-maxage=86400 Date: Wed, 01 Sep 2021 07:51:40 GMT Vary: Origin Vary: X-User-Context-Hash Vary: X-Editorial-Mode X-Cache-Debug: 1 Surrogate-Key: ez-all ``` The `Cache-Control` and `Vary` headers look correct. The request is handled by a custom controller and the `Surrogate-Key` only contains the default `ez-all` value. This isn't a problem as long as the controller doesn't return values from any content in the Ibexa DXP repository. If it does, the controller should also add the corresponding IDs to such objects in that header. The `Set-Cookie` here may cause the problem. A ESI fragment should never set a cookie because: - Clients only receive the headers set in the "mother" document (the headers in the "/" response in this case). - Only the content of ESIs responses is returned to the client. **No headers set in the ESI response ever reach the client**. ESI headers are only seen by the HTTP cache. - Symfony reverse proxy doesn't support ESIs at all, and any ESI calls (`render_esi()`) are implicitly replaced by sub-requests (`render()`). So any `Set-Cookie` **is** sent to the client when using Symfony reverse proxy. - Fastly flags it resource as "not cacheable" because it set a cookie at least once. Even though that endpoint stops setting cookies, Fastly still doesn't cache that fragment. Any document referring to that ESI is a `MISS`. Fastly cache needs to be purged (`Purge-all` request) to remove this flag. - It means that it's not recommended to always initiate a session when loading the front page. You must ensure that you don't unintendedly start a session in a controller used by ESIs, for example, when trying to access as session variable before a session has been initiated yet. # Configure and customize Fastly You can configure Fastly by using API calls or through the Fastly Web Interface. Fastly provides a [Fastly CLI](https://www.fastly.com/documentation/reference/cli/) for configuring Fastly through its API. Ibexa Cloud is delivered with Fastly preconfigured. It means that you don't have to do any changes to the Fastly configuration to make your site work. The information provided here is only applicable if you want to change the default Fastly configuration on Ibexa Cloud, or if you're not using Ibexa Cloud and want to configure Fastly to work with Ibexa DXP on premise. > **Note: The Fastly Web Interface isn't available for Ibexa Cloud** > > It's recommend for Ibexa Cloud customers to use the Fastly CLI instead of using the Fastly API directly with `curl`, or other alternatives. > **Note: Disable Varnish when you use Fastly** > > Varnish is automatically provisioned on Ibexa Cloud. Varnish needs to be disabled on all environments that use Fastly. See [documentation on how to do that](https://fixed.docs.upsun.com/guides/ibexa/fastly.html). ## Prepare for using Fastly locally These steps aren't needed when you use Ibexa Cloud, because Fastly is preconfigured in it. ### Get Fastly credentials from Ibexa Cloud installation To use Fastly CLI or Fastly API directly, you need to obtain the credentials for your site. To obtain the credentials, connect to your Fastly-enabled environment (for example, production or staging) through SSH and run the following command: ``` declare|grep FASTLY FASTLY_KEY=... FASTLY_SERVICE_ID=... ``` These credentials are different for your production and staging environments. When you configure the Fastly CLI, use the credentials for the environment that you want to change. > **Note: Different environment variable names between products** > > When you configure Fastly CLI, you use the `FASTLY_API_TOKEN` variable to store the token, while with Ibexa DXP you use `FASTLY_KEY` for the same purpose. ### Quickly configure Fastly for use with Ibexa DXP Use the commands below to install VCL configuration required for running Fastly with Ibexa DXP. You also need to set up domains, HTTPS and origin configuration (not covered here). All commands are explained in detail [below](#view-and-modify-vcl-configuration): ``` fastly vcl custom create --name=ibexa_main.vcl --version=active --autoclone --content=vendor/ibexa/fastly/fastly/ibexa_main.vcl --main fastly vcl custom create --name=ibexa_user_hash.vcl --content=vendor/ibexa/fastly/fastly/ibexa_user_hash.vcl --version=latest fastly vcl snippet create --name="Re-Enable shielding on restart" --version=latest --priority 100 --type recv --content=vendor/ibexa/fastly/fastly/snippet_re_enable_shielding.vcl fastly service-version activate --version=latest ``` ## Quick introduction to Fastly CLI Fastly configuration is versioned, which means that when you alter the configuration, you create a new version and activate it. If needed, you can revert the configuration to one of previous versions at any point. ### List configuration versions ``` fastly service-version list NUMBER ACTIVE LAST EDITED (UTC) 1 false 2023-07-03 10:01 2 false 2023-07-03 10:35 3 false 2023-07-03 11:00 4 false 2023-07-03 11:28 5 false 2023-07-03 10:58 6 false 2023-07-03 11:59 7 false 2023-07-03 12:13 8 true 2023-07-03 12:13 ``` In the example above, version 8 is used (ACTIVE=true). ### Create new configuration version A version that is ACTIVE cannot be modified. To change the configuration, you need to create a new version: Clone the current active version: ``` fastly service-version clone --version=active ``` Clone a particular version: ``` fastly service-version clone --version=4 ``` Clone the newest version: ``` fastly service-version clone --version=latest ``` > **Note: Command parameters** > > Most Fastly CLI commands have the `--version` parameter. In addition to a specific version number, the `--version` parameter always supports aliases like `active` and `latest`. > > Most Fastly CLI commands that alter the config also support the `--autoclone` parameter. With such commands, when you use the `--autoclone` parameter, calling `fastly service-version clone` is no longer needed. ### Activate version Activate a version with this command: ``` fastly service-version activate --version=latest ``` ## View and modify VCL configuration Fastly configuration is stored in Varnish Configuration Language (VCL) files. You can change the behaviour of Fastly by [uploading custom VCL files](https://www.fastly.com/documentation/guides/full-site-delivery/custom-vcl/uploading-custom-vcl). Ibexa DXP ships with two VCL files that need to be enabled for Fastly to work correctly with the platform; `ibexa_main.vcl` and `ibexa_user_hash.vcl` (located in `vendor/ibexa/fastly/fastly/`) ### List custom `.vcl` files for specific version ``` fastly vcl custom list --version 77 SERVICE ID VERSION NAME MAIN 4SEKDky8P3wdrctwZCi1C1 77 ibexa_main.vcl true 4SEKDky8P3wdrctwZCi1C1 77 ibexa_user_hash.vcl false ``` ### Get ibexa_main.vcl for specific version ``` fastly vcl custom describe --name=ibexa_main.vcl --version=77 Service ID: 4SEKDky8P3wdrctwZCi1C1 Service Version: 77 Name: ibexa_main.vcl Main: true Content: include "ibexa_user_hash.vcl" sub vcl_recv { (....) ``` ### Provide description for specific version For each version, you can provide a description that explains what changed in that version: ``` fastly service-version update --version=52 --comment="Added support for basic-auth on the staging domain" ``` #### List descriptions for all versions You can list the descriptions by adding the `--verbose` (`-v`) option to the `service-version list` command: ``` fastly service-version list -v Fastly API token provided via FASTLY_API_TOKEN Fastly API endpoint: https://api.fastly.com Service ID (via FASTLY_SERVICE_ID): KlUh0J1fnw1JY1aEQ0up Versions: 8 Version 1/8 Number: 1 Comment: Initial config Service ID: KlUh0J1fnw1JY1aEQ0up Active: false Locked: true Deployed: false Staging: false Testing: false Created (UTC): 2023-07-03 08:50 Last edited (UTC): 2023-07-03 10:01 Version 2/8 Number: 2 Comment: Fixed name of origin Service ID: KlUh0J1fnw1JY1aEQ0up Active: false Locked: true Deployed: false Staging: false Testing: false Created (UTC): 2023-07-03 10:01 Last edited (UTC): 2023-07-03 10:35 (...) ``` ### Modify Fastly configuration You can modify the existing Fastly configuration, for example, by uploading a modified `.vcl` file. Create a new version based on the one that is currently active, and upload the file: ``` fastly vcl custom update --name=ibexa_main.vcl --version=active --autoclone --content=vendor/ibexa/fastly/fastly/ibexa_main.vcl ``` Provide a description of the change in Fastly's version system: ``` fastly service-version update --version=latest --comment="Added feature X" ``` Activate the new version: ``` fastly service-version activate --version=latest ``` ## Snippets You can also add VCL code to the Fastly configuration without modifying the custom `.vcl` files directly. You do it by creating [snippets](https://www.fastly.com/documentation/guides/full-site-delivery/custom-vcl/about-vcl-snippets). it's recommended that you use snippets instead of changing the VCL files provided by Ibexa DXP as much as possible, which makes it easier to upgrade the Ibexa DXP VCL configuration later. When you use snippets, the snippet code is injected into the VCL where the `#FASTLY ...` macros are placed. For example, if you create a snippet for the `recv` subroutine, it's injected into the `ibexa_main.vcl` file, the line where `#FASTLY recv` is found. ### List available snippets for specific version ``` fastly vcl snippet list --version=active SERVICE ID VERSION NAME DYNAMIC SNIPPET ID KlUh0J1fnw1JY1aEQ0up 8 Re-Enable shielding on restart false 1iJWIfsPLNGxcphsjggq ``` > **Note: Note** > > As of version 3.3.24, 4.1.6 and 4.2.0, Ibexa DXP also requires one snippet to be installed, in addition to the custom VCLs `ibexa_main.vcl` and `ibexa_user_hash.vcl`. That snippet is by default named `Re-Enable shielding on restart`. ### Get details of installed snippets Use the `vcl snippet list` command with the `--verbose` option to get information such as: priority, which subroutine it's attached to (for example, `vcl_recv` or `vcl_fetch`) and the code itself. ``` fastly vcl snippet list --version=active -v Fastly API token provided via FASTLY_API_TOKEN Fastly API endpoint: https://api.fastly.com Service ID (via FASTLY_SERVICE_ID): [....] Service Version: 8 Name: Re-Enable shielding on restart ID: 1iJWIfsPLNGxcphsjggq Priority: 100 Dynamic: false Type: recv Content: // This code should be added as a snippet in your config: // Name: Re-Enable shielding on restart // Priority: 100 // Type: recv // // Fastly CLI: // - fastly vcl snippet create --name="Re-Enable shielding on restart" --version=active --autoclone --priority 100 --type recv --content=vendor/ibexa/fastly/fastly/snippet_re_enable_shielding.vcl // - fastly service-version activate --version=latest set var.fastly_req_do_shield = (req.restarts <= 2); # set var.fastly_req_do_shield = (req.restarts > 0 && req.http.accept == "application/vnd.fos.user-context-hash"); set req.http.X-Snippet-Loaded = "v1"; Created at: 2022-06-23 10:55:34 +0000 UTC Updated at: 2022-06-23 12:24:48 +0000 UTC ``` You can also get the same details for a particular snippet using the `vcl snippet describe` command. ### Get specific snippet ``` fastly vcl snippet describe --name="Re-Enable shielding on restart" --version=latest ``` ### Create snippet ``` fastly vcl snippet create --name="Re-Enable shielding on restart" --version=active --autoclone --priority 100 --type recv --content=vendor/ibexa/fastly/fastly/snippet_re_enable_shielding.vcl fastly service-version activate --version=latest ``` ### Update existing snippet ``` fastly vcl snippet update --name="Re-Enable shielding on restart" --version=active --autoclone --priority 100 --type recv --content=vendor/ibexa/fastly/fastly/snippet_re_enable_shielding.vcl fastly service-version activate --version=latest ``` ### Delete snippet ``` fastly vcl snippet delete --name="Re-Enable shielding on restart" --version=active --autoclone fastly service-version activate --version=latest ``` ### Get diff between two versions You can view the diff between two different versions by using the Fastly web interface. Unfortunately, Fastly CLI doesn't support this functionality. However, Fastly API and GNU diff can help you get an identical result. Use the Fastly API to download the generated `.vcl` file. It includes the VCL configuration that Fastly generates based on all the configuration settings (from all custom `.vcl` files, snippets, and origin configuration). The example below extracts the generated VCL for version no. 11 of some service: ``` curl -i "https://api.fastly.com/service/[FASTLY_SERVICE_ID]/version/11/generated_vcl" -H "Fastly-Key: [FASTLY_API_TOKEN]" -H "Accept: application/json" > generated_vcl_11_raw cp generated_vcl_11_raw generated_vcl_11_json_only ``` Next, you need to edit `generated_vcl_11_json_only` in your favourite editor, remove anything before the json data and save. Then, follow the same steps again for version no. 12 (or whatever version you want to diff version 11 against). Then replace `\n` in the files to get human-readable diffs: ``` cat generated_vcl_11_json_only |jq .content|perl -pe 's/\\n/\n/g' > generated_vcl_11_json_done cat generated_vcl_12_json_only |jq .content|perl -pe 's/\\n/\n/g' > generated_vcl_12_json_done ``` Finally, you can use GNU diff to get a readable diff of the two versions: ``` diff -ruN generated_vcl_11_json_done generated_vcl_12_json_done ``` ## Enable basic-auth on Fastly To enable basic-auth, use [Fastly documentation](https://www.fastly.com/documentation/solutions/examples/http-basic-auth/) as an example. Follow the steps below. Usernames and passwords can be stored inside the VCL file, but in this case credentials are stored in a [dictionary](https://www.fastly.com/documentation/guides/full-site-delivery/dictionaries/working-with-dictionaries#working-with-dictionaries-using-vcl-snippets). > **Note: Note** > > To make this example work, you must run Ibexa DXP in version 3.3.16 or later, or 4.5. ### Create and activate dictionary Fastly configuration includes a dictionary named `basicauth`. Using a dictionary instead of storing usernames directly in a `.vcl` file is beneficial, because you can add or remove records without having to create and activate new configuration versions. ``` fastly dictionary create --version=active --autoclone --name=basicauth fastly service-version activate --version=latest ``` ### Get dictionary ID To add users to the dictionary, first get the dictionary ID. ``` fastly dictionary list --version=active Service ID: KlUh0J1fnw1JY1aEQ0up Version: 3 ID: ltC6Rg4pqw4qaNKF5tEW Name: basicauth Write Only: false Created (UTC): 2023-07-03 10:33 Last edited (UTC): 2023-07-03 10:33 ``` In the example above, the ID is `ltC6Rg4pqw4qaNKF5tEW`. ### Create record in dictionary Add username and password to the dictionary: ``` fastly dictionary-entry create --dictionary-id=ltC6Rg4pqw4qaNKF5tEW --key=user1 --value=foobar1 ``` ### List dictionary records You can list the records from a dictionary by using the following command: ``` fastly dictionary-entry list --dictionary-id=ltC6Rg4pqw4qaNKF5tEW33 ``` Now your dictionary stores new username and password. The next thing to do is to alter the Fastly VCL configuration and add the basic-auth support. This example uses [snippets](https://www.fastly.com/documentation/guides/full-site-delivery/custom-vcl/about-vcl-snippets), so that no changes are needed in the `.vcl` files that are shipped with Ibexa DXP. You need two snippets, store these as files in your system: In `snippet_basic_auth_error.vcl`: ``` // This code should be added as a snippet in your config: // Name: BasicAuth error // Priority: 100 // Type: error // See snippet_basic_auth_recv.vcl for installation instructions // # If status code is a 401, a synthetic HTML page with this error is served to the user. if (obj.status == 401) { set obj.http.Content-Type = "text/html; charset=utf-8"; set obj.http.WWW-Authenticate = "Basic realm=MYREALM"; synthetic {" Error

    401 Unauthorized (Fastly)

    "}; return (deliver); } ``` In `snippet_basic_auth_recv.vcl`: ``` // This code should be added as a snippet in your config: // Name: BasicAuth recv // Priority: 100 // Type: recv // // Fastly CLI: // - fastly vcl snippet create --name="BasicAuth recv" --version=active --autoclone --priority 100 --type recv --content=snippet_basic_auth_recv.vcl // - fastly vcl snippet create --name="BasicAuth error" --version=latest --priority 100 --type error --content=snippet_basic_auth_error.vcl // - fastly service-version activate --version=latest declare local var.credential STRING; declare local var.username STRING; declare local var.password STRING; declare local var.result STRING; # Basic auth is checked on edge nodes only. The logic below makes sure that it's only run at the edge. if (fastly.ff.visits_this_service == 0 && req.restarts == 0) { if (req.http.Authorization ~ "(?i)^Basic ([a-z0-9_=]+)$") { set var.credential = digest.base64_decode(re.group.1); set var.username = if(var.credential ~ "^(.+?):.+$", re.group.1, ""); set var.password = if(var.credential ~ "^.+?:(.+)$", re.group.1, ""); set var.result = table.lookup(basicauth, var.username, "NOTFOUND"); if (var.result == "NOTFOUND") { error 401 "Restricted"; } else if (var.result != var.password) { error 401 "Restricted"; } # The Auth header is unset to avoid exposing it as a response header. unset req.http.Authorization; set req.http.Auth-User = var.username; } else { error 401 "Restricted"; } } # Unsetting req.http.Authorization to avoid reaching "return(pass)" in vcl_recv for the first ESI request if (req.is_esi_subreq) { unset req.http.Authorization; } ``` To enable basic-auth for one domain only, alter `snippet_basic_auth_recv.vcl`: ``` -if (fastly.ff.visits_this_service == 0 && req.restarts == 0 &&) { +if (fastly.ff.visits_this_service == 0 && req.restarts == 0 && req.http.host == "example.com") { ``` Install the snippets with the following Fastly CLI command: ``` fastly vcl snippet create --name="BasicAuth recv" --version=active --autoclone --priority 100 --type recv --content=snippet_basic_auth_recv.vcl fastly vcl snippet create --name="BasicAuth error" --version=latest --priority 100 --type error --content=snippet_basic_auth_error.vcl fastly service-version activate --version=latest ``` # Persistence cache *[Image: SPI cache diagram]* ## Layers Persistence cache can best be described as an implementation of `SPI\Persistence` that decorates the main backend implementation, aka Storage Engine *(currently: "Legacy Storage Engine")*. As shown in the illustration, this is done in the exact same way as the event layer is a custom implementation of `API\Repository` decorating the main repository. In the case of persistence cache, instead of sending events on calls passed on to the decorated implementation, most of the load calls are cached, and calls that perform changes purge the affected caches. Cache handlers *(for example, Redis, or Filesystem)* can be configured using Symfony configuration. For details on how to reuse this Cache service in your own custom code, see below. ## Transparent cache With the persistence cache, like with the HTTP cache, Ibexa DXP tries to follow principles of transparent caching. This can shortly be described as a cache which is invisible to the end user (admin/editors) of Ibexa DXP where content is always returned *fresh*. In other words, there should be no need to manually clear the cache like it was frequently the case with eZ Publish 4.x. This is possible thanks to an interface that follows CRUD (Create Read Update Delete) operations per domain. ## What is cached? Persistence cache aims at caching most `SPI\Persistence` calls used in common page loads, including everything needed for permission checking and URL alias lookups. Notes: - [Cache tagging](https://symfony.com/doc/7.4/components/cache/cache_invalidation.html#using-cache-tags) is used in order to allow clearing cache by alternative indexes. For instance tree operations or changes to content types are examples of operations that also need to invalidate content cache by tags. - Search isn't defined as persistence and the queries themselves aren't planned to be cached as they're too complex by design (for example, full text). Use [Solr](https://doc.ibexa.co/en/latest/search/search_engines/solr_search_engine/solr_overview/index.md) which caches this for you to improve scale/performance, and to offload your database. For further details on which calls are cached or not, see details in the [Symfony Web Debug Toolbar](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/devops/#web-debug-toolbar) which has info on cache use in two places: - Symfony Cache tab: for Symfony Cache itself, the tab shows cache lookups to cache backends - Ibexa tab: shows calls made to database back end, and if they're cached or not To see where and how to contribute additional caches, refer to the [source code](https://github.com/ibexa/core/blob/main/src/lib/Persistence/Cache/Readme.md). ## Persistence cache configuration > **Note: Note** > > Current implementation uses Symfony cache. It technically supports the following cache backends: [APCu, Array, Chain, Doctrine, Filesystem, PDO & Doctrine DBAL, Php Array, Proxy, Redis](https://symfony.com/doc/7.4/components/cache/cache_pools.html#creating-cache-pools). Ibexa DXP officially supports only using Filesystem for single server and Redis for clustered setups. Use of Redis as shared cache back end is a requirement for use in clustering setup. For an overview of this feature, see [Clustering](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/clustering/clustering/index.md). Filesystem adapters, for example, are **not** intended to be used over a shared filesystem. **Cache service** The underlying cache system is exposed as an `ibexa.cache_pool` service, and can be reused by any other service as described in the [Using Cache service](#using-cache-service) section. By default, configuration uses the `cache.tagaware.filesystem` service to store cache files. The service is defined in `config/packages/cache_pool/cache.tagaware.filesystem.yaml` to use [FilesystemTagAwareAdapter](https://github.com/ibexa/recipes/blob/master/ibexa/oss/4.0/config/packages/cache_pool/cache.tagaware.filesystem.yaml#L8). You can select a different cache backend and configure its parameters in the relevant file in the `cache_pool` folder. ### Multi repository setup You can [configure multisite to work with multiple Repositories](https://doc.ibexa.co/en/latest/administration/configuration/repository_configuration/#defining-custom-connection). Then, in configuration you can specify which cache pool you want to use on a SiteAccess or SiteAccess group level. The following example shows use in a SiteAccess group: ``` ibexa: system: # "site_group" refers to the group configured in site access site_group: # cache_pool is set to '%env(CACHE_POOL)%' # env(CACHE_POOL) is set to 'cache.tagaware.filesystem' (a Symfony service) by default, for more examples see config/packages/cache_pool/* cache_service_name: '%cache_pool%' ``` > **Note: One cache pool for each repository** > > If your installation has several repositories *(databases)*, make sure every group of sites that uses different repositories also uses a different cache pool. ### In-Memory cache configuration Persistence cache layer caches selected objects in-memory for a short time. It avoids loading repeatedly the same data from, for example, a remote Redis instance, which can take up to 4-5ms per call due to the network latency and Redis instance load. The cache is organized in 2 pools, one for metadata which isn't updated frequently, and one for content related objects that is only meant as a short-lived burst cache. Limit is organized using a [least frequently used (LFU)](https://en.wikipedia.org/wiki/Least_frequently_used) approach. It makes sure repeatedly used objects stay in-memory until expired, and those seldom used are bulk evicted from cache every time the maximum number of cache items is reached. This in-memory cache is purged *(for the current PHP process)* when clearing it using any of the mentioned methods below. For other processes, the object is refreshed when it expires or evicted when it reaches the cache limits. In-Memory cache is configured globally, and has the following default settings: ``` parameters: # Config for metadata cache pool, here showing default config # ttl: Maximum number of milliseconds objects are kept in-memory (3000ms = 3s) ibexa.spi.persistence.cache.inmemory.ttl: 3000 # limit: Maximum number of cache objects to place in-memory, to avoid consuming too much memory ibexa.spi.persistence.cache.inmemory.limit: 100 # enabled: Is the in-memory cache enabled ibexa.spi.persistence.cache.inmemory.enable: true # Config for content cache pool, here showing default config ## WARNING: TTL is on purpose low to avoid getting outdated data in prod! For dev environment, you can safely increase it (e.g. by x3) ibexa.spi.persistence.cache.inmemory.content.ttl: 300 ibexa.spi.persistence.cache.inmemory.content.limit: 100 ibexa.spi.persistence.cache.inmemory.content.enable: true ``` > **Caution: In-Memory cache is per-process** > > **TTL and Limit need to have a low value.** Setting limit high increases memory use. High TTL value also increases exponentially risk for system acting on stale metadata (for example, content type definitions). The only case where it's safe to increase these values is for dev environment with single concurrency on writes. In prod environment you should only consider reducing them if you have heavy concurrency writes. ### Redis/Valkey [Redis](https://redis.io/), an in-memory data structure store, is one of the supported cache solutions for clustering. Redis is used via [Redis pecl extension](https://pecl.php.net/package/redis). See \[Redis Cache Adapter in Symfony documentation\]( for information on how to connect to Redis. [Valkey](https://valkey.io/), an alternative data structure store compatible with Redis, is also supported. To set it up with Ibexa DXP, follow the same steps as for Redis. #### Supported Adapters There are two Redis adapters available out of the box that fit different needs. ##### `Symfony\Component\Cache\Adapter\RedisTagAwareAdapter` **Requirement**: Redis server configured with eviction [`maxmemory-policy`](https://redis.io/docs/latest/develop/reference/eviction/#eviction-policies): `volatile-ttl`, `volatile-lru` or `volatile-lfu` (Redis 4.0+). Use of LRU or LFU is recommended. it's also possible to use `noeviction`, but it's usually not practical. **Pros**: It's typically faster than `RedisAdapter`, because fewer lookups needed to cache backend. **Cons**: Consumes much more memory. To avoid situations where Redis stops accepting new cache (warnings about `Failed to save key`), set aside enough memory for the Redis server. ##### `Symfony\Component\Cache\Adapter\RedisAdapter` **Pros**: Uses a bit less memory than `RedisTagAwareAdapter`, so it eliminated the risk of stopping saving cache when there isn't enough memory. **Cons**: 1.5-2x more lookups to the back-end cache server then `RedisTagAwareAdapter`. Depending on the number of lookups and latency to cache server this might affect page load time. #### Adjusting configuration A default example that you can use out-of-the-box is found in `config/packages/cache_pool/cache.redis.yaml`. > **Note: Ibexa Cloud** > > For Ibexa Cloud installations, the [`ibexa/cloud` package](https://doc.ibexa.co/en/latest/ibexa_cloud/install_on_ibexa_cloud/index.md) performs configuration based on the `.platform.app.yaml` file. For anything else, you can enable it with environment variables. For instance, if you set the following environment variables `export CACHE_POOL="cache.redis" CACHE_DSN="secret@example.com:1234/13"`, it results in config like this: ``` services: cache.redis: # NOTE: Available via https://github.com/symfony/cache class: Symfony\Component\Cache\Adapter\RedisTagAwareAdapter parent: cache.adapter.redis tags: - name: cache.pool clearer: cache.app_clearer provider: 'redis://secret@example.com:1234/13' # Default CACHE_NAMESPACE value, see config/cache_pool/cache.redis.yaml for usage with e.g. multi repo. namespace: 'ezp' ``` See `.env`, `config/packages/ibexa.yaml` and `config/packages/cache_pool/cache.redis.yaml` for further details on `CACHE_POOL`, `CACHE_DSN` and `CACHE_NAMESPACE`. > **Caution: Clearing Redis cache** > > The regular `php bin/console cache:clear` command doesn't clear Redis persistence cache. Use a dedicated Symfony command to clear the pool you have configured: `php bin/console cache:pool:clear cache.redis`. ##### Redis clustering Persistence cache depends on all involved web servers, each of them seeing the same view of the cache because it's shared among them. With that in mind, the following configurations of Redis are possible: - [Redis Cluster](https://redis.io/docs/latest/operate/oss_and_stack/management/scaling/) - Shards cache across several instances to be able to cache more than memory of one server allows - Shard slaves can improve availability, however [they use asynchronous replication](https://redis.io/docs/latest/operate/oss_and_stack/management/scaling/#redis-cluster-consistency-guarantees) so they can't be used for reads - Unsupported Redis features that can affect performance: [pipelining](https://github.com/phpredis/phpredis/blob/develop/cluster.md#pipelining) and [most multiple key commands](https://github.com/phpredis/phpredis/blob/develop/cluster.md#multiple-key-commands) - [Redis Sentinel](https://redis.io/docs/latest/operate/oss_and_stack/management/sentinel/) - Provides high availability by providing one or several slaves (ideally 2 slaves or more, for example, minimum 3 servers), and handle failover - [Slaves are asynchronously replicated](https://redis.io/docs/latest/operate/oss_and_stack/management/sentinel/#fundamental-things-to-know-about-sentinel-before-deploying), so they can't be used for reads - Typically used with a load balancer (for example, HAProxy with occasional calls to Redis Sentinel API) in the front to only speak to elected master - As of v3 you can also configure this [directly on the connection string](https://symfony.com/doc/7.4/components/cache/adapters/redis_adapter.html#configure-the-connection), **if** you use `Predis` instead of `php-redis` Several cloud providers have managed services that are easier to set up, handle replication and scalability for you, and might perform better. Notable services include: - [Amazon ElastiCache](https://aws.amazon.com/elasticache/) - [Azure Redis Cache](https://azure.microsoft.com/en-us/products/cache/) - [Google Cloud Memorystore](https://cloud.google.com/memorystore) ###### Ibexa Cloud usage > **Note: Ibexa Cloud** > > If you use Upsun Enterprise you can benefit from the Redis Sentinel across three nodes for great fault tolerance. Upsun Professional and lower versions offer Redis in single instance mode only. ## Using cache service Using the internal cache service allows you to use an interface and without caring whether the system is configured to place the cache in Redis or on File system. And as Ibexa DXP requires that instances use a cluster-aware cache in cluster setup, you can safely assume your cache is shared *(and invalidated)* across all web servers. > **Note: Note** > > Current implementation uses a caching library implementing TagAwareAdapterInterface which extends `Psr\Cache\CacheItemPoolInterface`, and therefore is compatible with PSR-6. > **Caution: Use unique vendor prefix for Cache key** > > When reusing the cache service within your own code, it's very important to not conflict with the cache keys used by others. That is why the example of usage below starts with a unique `myApp` key. For the namespace of your own cache, you must do the same. #### Getting cache service ##### With dependency injection In your Symfony services configuration you can define that you require the cache service in your configuration like so: ``` # yml configuration App\MyService: arguments: - '@ibexa.cache_pool' ``` This service is an instance of `Symfony\Component\Cache\Adapter\TagAwareAdapterInterface`, which extends the `Psr\Cache\CacheItemPoolInterface` interface with tagging functionality. ##### With service container Like any other service, you can also get the cache service with the [service container](https://doc.ibexa.co/en/latest/api/php_api/php_api/#service-container) like so: ``` // Getting the cache service in PHP /** @var \Symfony\Component\Cache\Adapter\TagAwareAdapterInterface */ $pool = $container->get('ibexa.cache_pool'); ``` ### Using the cache service Example usage of the cache service: ``` // Example $cacheItem = $pool->getItem("myApp-object-${id}"); if ($cacheItem->isHit()) { return $cacheItem->get(); } $myObject = $container->get('my_app.backend_service')->loadObject($id) $cacheItem->set($myObject); $cacheItem->tag(['myApp-category-' . $myObject->categoryId]); $pool->save($cacheItem); return $myObject; ``` For more info on usage, see [Symfony Cache's documentation](https://symfony.com/doc/7.4/components/cache.html). ### Clearing persistence cache Persistence cache prefixes it's cache using "ibx-". Clearing persistence cache can thus be done in the following ways: ``` // To clear all cache (not recommended without a good reason) $pool->clear(); // To clear a specific cache item (check source for more examples in Ibexa\Core\Persistence\Cache\*) $pool->deleteItems(["ibx-ci-$contentId"]); // Symfony cache is tag-based, so you can clear all cache related to a content item like this: $pool->invalidateTags(["c-$contentId"]); ``` # Clustering Clustering in Ibexa DXP refers to setting up your installation with several web servers for handling more load and/or for failover. ## Server setup overview This diagram illustrates how clustering in Ibexa DXP is typically set up. The parts illustrate the different roles needed for a successful cluster setup. *[Image: Server setup for clustering]* The number of web servers, Redis/Valkey, Solr, Varnish, Database, and NFS servers, but also whether some servers play several of these roles (typically running Redis/Valkey across the web server), is up to you and your performance needs. The minimal requirements are: - [Shared HTTP cache (using Varnish)](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/http_cache/reverse_proxy/#using-varnish-or-fastly) - [Shared persistence cache](#shared-persistence-cache) and [sessions](#shared-sessions) (using Redis/Valkey) - Shared database (using MySQL/MariaDB) - [Shared binary files](#shared-binary-files) (using NFS, or S3) For more information on requirements, see [Requirements page](https://doc.ibexa.co/en/latest/getting_started/requirements/index.md). It's also recommended to use: - [Solr](https://doc.ibexa.co/en/latest/search/search_engines/solr_search_engine/solr_overview/index.md) or [Elasticsearch](https://doc.ibexa.co/en/latest/search/search_engines/elasticsearch/elasticsearch_overview/index.md) for better search and performance - a CDN for improved performance and faster ping time worldwide - you can use Fastly, which has native support as HTTP cache and CDN. - active/passive database for failover - more recent versions of PHP and MySQL/MariaDB within [what is supported](https://doc.ibexa.co/en/latest/getting_started/requirements/index.md) for your Ibexa DXP version to get more performance out of each server. Numbers might vary so make sure to test this when upgrading. ### Shared persistence cache Redis and Valkey are the recommended cache solutions for clustering. See [persistence cache documentation](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/persistence_cache/#persistence-cache-configuration) on information on how to configure them. ### Shared sessions For a [cluster](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/clustering/clustering/index.md) setup you need to configure sessions to use a back end that is shared between web servers. The main option out of the box in Symfony is the PHP Redis session handler (also compatible with Valkey). Alternatively, there is Symfony session handler for PDO (database). To avoid concurrent access to session data from front-end nodes, if possible you should either: - Enable [Session locking](https://www.php.net/manual/en/features.session.security.management.php#features.session.security.management.session-locking) - Use "Sticky Session", aka [Load Balancer Persistence](https://en.wikipedia.org/wiki/Load_balancing_%28computing%29#Persistence) Session locking is available with `php-redis` (v4.2.0 and higher). On Ibexa Cloud (and Upsun) Redis and Valkey are preferred and supported. ### Shared binary files Ibexa DXP supports multi-server setups by means of [custom IO handlers](https://doc.ibexa.co/en/latest/content_management/file_management/file_management/#dfs-cluster-handler). They make sure that files are correctly synchronized among the multiple clients using the data. ## DFS IO handler The DFS IO handler (`legacy_dfs_cluster`) can be used to store binary files on an NFS server. It uses a database to manipulate metadata, making up for the potential inconsistency of network-based filesystems. ### Configuring the DFS IO handler You need to configure both metadata and binarydata handlers. Ibexa DXP ships with a custom local adapter (`ibexa.io.nfs.adapter.site_access_aware`), which decorates the Flysystem v2 local adapter to enable support for SiteAccess-aware settings. If an NFS path relies on SiteAccess-aware dynamic parameters, you must use the custom local adapter instead of the Flysystem v2 local adapter. Configure the custom local adapter to read/write to the NFS mount point on each local server. As metadata handler, create a DFS one, configured with a Doctrine connection. > **Tip: Tip** > > The default database install now includes the dfs table *in the same database* First, define DFS folder path as a variable in `.env` file: `DFS_NFS_PATH=` Next, if you're using a separate DFS database, configure it via the `DATABASE_URL` variable in the `.env` file. Depending on which database you're using: `DFS_DATABASE_URL=mysql://root:rootpassword@127.0.0.1:3306/ibexa_dfs?serverVersion=8.0` or `DATABASE_URL=postgresql://root:rootpassword@127.0.0.1:5432/ibexa_dfs?serverVersion=14.18` For production, it's recommended to create the DFS table in its own database, manually importing its schema definition: > **Note: dfs_schema.sql (MySQL)** > > ``` > CREATE TABLE ibexa_dfs_file ( > name text NOT NULL, > name_trunk text NOT NULL, > name_hash varchar(34) NOT NULL DEFAULT '', > datatype varchar(255) NOT NULL DEFAULT 'application/octet-stream', > scope varchar(25) NOT NULL DEFAULT '', > size bigint(20) unsigned NOT NULL DEFAULT '0', > mtime int(11) NOT NULL DEFAULT '0', > expired tinyint(1) NOT NULL DEFAULT '0', > status tinyint(1) NOT NULL DEFAULT '0', > PRIMARY KEY (name_hash), > KEY ibexa_dfs_file_name (name (191)), > KEY ibexa_dfs_file_name_trunk (name_trunk (191)), > KEY ibexa_dfs_file_mtime (mtime), > KEY ibexa_dfs_file_expired_name (expired,name (191)) > ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; > ``` > **Note: dfs_schema.sql (PostgreSQL)** > > ``` > CREATE TABLE ibexa_dfs_file ( > name_hash varchar(34) DEFAULT '' NOT NULL, > name text NOT NULL, > name_trunk text NOT NULL, > datatype varchar(255) DEFAULT 'application/octet-stream' NOT NULL, > scope character varying(25) DEFAULT '' NOT NULL, > size bigint DEFAULT 0 NOT NULL, > mtime integer DEFAULT 0 NOT NULL, > expired boolean DEFAULT false NOT NULL, > status boolean DEFAULT false NOT NULL > ); > > ALTER TABLE ONLY ibexa_dfs_file > ADD CONSTRAINT ibexa_dfs_file_pkey PRIMARY KEY (name_hash); > > CREATE INDEX ibexa_dfs_file_expired_name ON ibexa_dfs_file USING btree (expired, name); > CREATE INDEX ibexa_dfs_file_mtime ON ibexa_dfs_file USING btree (mtime); > CREATE INDEX ibexa_dfs_file_name ON ibexa_dfs_file USING btree (name); > CREATE INDEX ibexa_dfs_file_name_trunk ON ibexa_dfs_file USING btree (name_trunk); > ``` This example uses Doctrine connection named `dfs`: ``` parameters: env(DFS_DATABASE_URL): '%env(resolve:DATABASE_URL)%' dfs_database_url: '%env(resolve:DFS_DATABASE_URL)%' ibexa.io.nfs.adapter.config: root: '%kernel.project_dir%/%env(string:DFS_NFS_PATH)%' path: '$var_dir$/$storage_dir$/' writeFlags: ~ linkHandling: ~ permissions: [ ] # new Doctrine connection for the DFS legacy_dfs_cluster metadata handler. doctrine: dbal: connections: dfs: # configure these for your database server driver: '%env(string:DFS_DATABASE_DRIVER)%' charset: '%env(string:DFS_DATABASE_CHARSET)%' default_table_options: charset: '%env(string:DFS_DATABASE_CHARSET)%' collate: '%env(string:DFS_DATABASE_COLLATION)%' url: '%env(string:DFS_DATABASE_URL)%' # define the Flysystem handler oneup_flysystem: adapters: nfs_adapter: custom: service: ibexa.io.nfs.adapter.site_access_aware # define the Ibexa handlers ibexa_io: binarydata_handlers: nfs: flysystem: adapter: nfs_adapter metadata_handlers: dfs: legacy_dfs_cluster: connection: doctrine.dbal.dfs_connection # set the application handlers ibexa: system: default: io: metadata_handler: dfs binarydata_handler: nfs ``` > **Tip: Tip** > > If you're looking to [set up S3](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/clustering/clustering_with_aws_s3/index.md) or other [Flysystem](https://flysystem.thephpleague.com/docs/)/third-party adapters like Google Cloud Storage, this needs to be configured as binary handler. The rest here still stays the same, the DFS metadata handler takes care of caching the lookups to avoid slow IO lookups. #### Customizing the storage directory Earlier versions required the NFS adapter directory to be set to `$var_dir$/$storage_dir$` part for the NFS path. It's no longer required, but the default prefix used to serve binary files still matches this expectation. If you decide to change this setting, make sure you also set `io.url_prefix` to a matching value. If you set the NFS adapter's directory to `/path/to/nfs/storage`, use this configuration so that the files can be served by Symfony: ``` ibexa: system: default: io: url_prefix: storage ``` As an alternative, you may serve images from NFS by using a dedicated web server. If in the example above, this server listens on `http://static.example.com/` and uses `/path/to/nfs/storage` as the document root, configure `io.url_prefix` as follows: ``` ibexa: system: default: io: url_prefix: 'http://static.example.com/' ``` You can read more about that on [Binary files URL handling](https://doc.ibexa.co/en/latest/content_management/file_management/file_url_handling/#file-url-handling). ### Web server rewrite rules The default Ibexa DXP rewrite rules let image requests be served directly from disk. In a cluster setup, files matching `^/var/([^/]+/)?storage/images(-versioned)?/.*` have to be passed through `/public/index.php` instead. In any case, this specific rewrite rule must be placed before the ones that "ignore" image files and let the web server serve the files directly. #### Apache ``` RewriteRule ^/var/([^/]+/)?storage/images(-versioned)?/.* /index.php [L] ``` Place this before the standard image rewrite rule in your vhost config (or uncomment if already there). #### nginx ``` rewrite "^/var/([^/]+/)?storage/images(-versioned)?/(.*)" "/index.php" break; ``` Place this before the include of `ibexa_params.d`/`ibexa_rewrite_params` in your vhost config (or uncomment if already there). ## Migrating to a cluster setup If you're migrating an existing single-server site to a cluster setup, and not setting up clustering from scratch, you need to migrate your files. Once you have configured your binarydata and metadata handlers, you can run the `ibexa:io:migrate-files` command. You can also use it when you're migrating from one data handler to another, for example, from NFS to Amazon S3. This command shows which handlers are configured: ``` > php bin/console ibexa:io:migrate-files --list-io-handlers Configured meta data handlers: default, dfs, aws_s3 Configured binary data handlers: default, nfs, aws_s3 ``` You can do the actual migration like this: ``` php bin/console ibexa:io:migrate-files --from=default,default --to=dfs,nfs --env=prod ``` The `--from` and `--to` values must be specified as `,`. If `--from` is omitted, the default IO configuration is used. If `--to` is omitted, the first non-default IO configuration is used. > **Tip: Tip** > > The command must be executed with the same permissions as the web server. While the command is running, the files should not be modified. To avoid surprises you should create a [backup](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/backup/index.md) and/or execute a dry run before doing the actual update, using the `--dry-run` switch. Since this command can run for a long time, to avoid memory exhaustion, use the `--env=prod` switch when you run it in the production environment. # Clustering with Amazon AWS S3 When setting up clustering, you can use Amazon AWS S3 as a binary handler, meaning AWS S3 is used to store binary files. > **Tip: Tip** > > Before you start, you should be familiar with the [clustering](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/clustering/clustering/index.md) documentation. ## Set up AWS S3 account 1. Go to  and create an account. An [existing Amazon account can be used](https://docs.aws.amazon.com/AmazonS3/latest/userguide/GetStartedWithS3.html#sign-up-for-aws). 2. [Choose a region](https://docs.aws.amazon.com/storagegateway/latest/vgw/available-regions-intro.html). The example below uses EU (Ireland): `eu-west-1` 3. Create a bucket in your chosen region and make note of the bucket name: . 4. Go to the [IAM Management Console](https://console.aws.amazon.com/iam/home#/users) and create a user. See . 5. Then create a group and assign the user to the group. 6. Assign policies to the group. The `AmazonS3FullAccess` policy gives read/write access to your bucket. 7. Still in the IAM console, view the user you created. Click the **Security credentials** tab. 8. Click "Create access key" and make note of the "Access key ID" and the "Secret access key". The secret key cannot be retrieved again after the key has been created, so don't lose it. (However, you can create new keys if needed.) > **Note: Note** > > Make sure that your bucket is [configured as Public](https://docs.aws.amazon.com/AmazonS3/latest/userguide/configuring-block-public-access-bucket.html) to avoid facing 403 errors, as the current S3 handler is meant to store files publicly so they can be served directly from S3. ## Set up Ibexa DXP for AWS S3 In your Ibexa DXP root directory, run `php composer require league/flysystem-aws-s3-v3:^2.0`. Then, register the AWS S3 client as a service: ``` services: Aws\S3\S3Client: arguments: - version: latest region: eu-west-1 # The region string of your chosen region credentials: key: ABCDEF... # Your AWS key ID secret: abc123... # Your AWS secret key ``` Set up the Flysystem v2 adapter that uses the S3 client under the `oneup_flysystem.adapters` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` oneup_flysystem: adapters: aws_s3_adapter: awss3v3: client: Aws\S3\S3Client bucket: my-bucket # Your bucket name prefix: 'my-prefix' # Your custom prefix, for example: 'my_site' ``` In the same place, set up the binary data handler for the S3 adapter: ``` ibexa_io: binarydata_handlers: aws_s3: flysystem: adapter: aws_s3_adapter ``` > **Note: Note** > > `aws_s3` is an arbitrary handler identifier that is used in the config block below. You can configure multiple handlers. > > For example, you could configure one called `gcloud_storage` for a [Google Cloud Storage adapter](https://github.com/thephpleague/flysystem#officially-supported-adapters). Under the `ibexa.system..io` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files), enable the binary data handler: ``` ibexa: system: default: io: binarydata_handler: aws_s3 # Also remember to use DFS for metadata_handler to avoid expensive lookups to S3 (see Clustering guide) # metadata_handler: dfs ``` Clear all caches and reload, and that's it. ## Migrate your existing binary data to S3 You can [migrate existing binary data](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/clustering/clustering/#migrating-to-a-cluster-setup) to S3 with the `php bin/console ibexa:io:migrate-files` command. # Clustering with DDEV > **Caution: Caution** > > Don't use this procedure in production. A staging environment for validation before production should exactly replicate the production environment. This is meant for development environment only. This guide follows [Install with DDEV](https://doc.ibexa.co/en/latest/getting_started/install_with_ddev/index.md) and helps to extend the previous installation to locally replicate a production [cluster](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/clustering/clustering/index.md). In contrast to a production cluster, this setup has only one front app server. But the data sharing needed by a cluster of several servers can still be emulated. The `ddev config --php-version` option should set the same PHP version as the production servers. > **Tip: Tip** > > - [`ddev describe`](https://ddev.readthedocs.io/en/latest/users/usage/commands/#describe) displays a cluster summary that include accesses from inside and outside DDEV services > - [`ddev ssh`](https://ddev.readthedocs.io/en/latest/users/usage/commands/#ssh) opens a terminal inside a service > - [`ddev exec`](https://ddev.readthedocs.io/en/latest/users/usage/commands/#exec) executes a command inside a service Discover more commands in [DDEV documentation](https://ddev.readthedocs.io/en/latest/users/usage/commands/). To run an Ibexa Cloud project locally, you may refer to [DDEV and Ibexa Cloud](https://doc.ibexa.co/en/latest/ibexa_cloud/ddev_and_ibexa_cloud/index.md) instead. ## Install reverse proxy A reverse proxy can be added to the cluster to enable [HTTP caching](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/http_cache/http_cache/index.md). ### Varnish The following sequence of commands: 1. Sets a variable with the desired Varnish version, here Varnish 7.1 2. Copies and customizes `parameters.vcl` file in `.ddev/varnish/` (which is mounted as `/etc/varnish/` into the container): - sets `web` container as the backend host and an invalidator (so back office can purge cache) - adds "all IPs" CIDR notation to `debuggers` list to allow debugging from any IP - on Varnish 7, enable logging of access control list matching for both `invalidators` and `debuggers` lists (new Varnish 7 syntax, it was enabled by default on previous versions) 1. Sets main `varnish*.vcl` file to use and "path to VCL directory" argument name depending on Varnish version 2. Copies the main VCL file to `.ddev/varnish/` 3. Sets the Varnish version to use and its demon starting parameters to use the files 4. Adds the Varnish container 5. Sets Varnish as the HTTP cache server 6. Restarts the DDEV cluster and clear the Ibexa DXP cache ``` VARNISH_VERSION=7.1 mkdir -p .ddev/varnish sed 's/.host = "127.0.0.1";/.host = "web";/' vendor/ibexa/http-cache/docs/varnish/vcl/parameters.vcl > .ddev/varnish/parameters.vcl sed -i '/^acl invalidators {$/a \\ "web";' .ddev/varnish/parameters.vcl sed -i '/^acl debuggers {$/a \\ "0.0.0.0"/0; \/\/ debug from any IP' .ddev/varnish/parameters.vcl if [[ $VARNISH_VERSION == 7.* ]]; then sed -i 's/acl invalidators {/acl invalidators +log {/' .ddev/varnish/parameters.vcl sed -i 's/acl debuggers {/acl debuggers +log {/' .ddev/varnish/parameters.vcl vcl_path=vcl_path vcl_file=varnish7.vcl elif [[ $VARNISH_VERSION == 6.* ]]; then vcl_path=vcl_dir vcl_file=varnish6.vcl fi cp vendor/ibexa/http-cache/docs/varnish/vcl/$vcl_file .ddev/varnish/ ddev dotenv set .ddev/.env.varnish --varnish-docker-image=varnish:$VARNISH_VERSION --varnish-varnishd-params " -p $vcl_path=/etc/varnish -f /etc/varnish/$vcl_file" ddev add-on get ddev/ddev-varnish ddev config --web-environment-add HTTPCACHE_PURGE_SERVER=http://varnish ddev config --web-environment-add HTTPCACHE_PURGE_TYPE=varnish ddev config --web-environment-add TRUSTED_PROXIES=varnish ddev restart ddev php bin/console cache:clear ``` To use Varnish 6.0LTS, set the following variable instead: ``` VARNISH_VERSION=6.0 ``` If you're using [Apache as web server](https://doc.ibexa.co/en/latest/getting_started/install_with_ddev/#switch-to-apache-and-its-virtual-host), you must set `varnish` as a trusted proxy in `.ddev/apache/apache-site.conf` before restarting DDEV: ``` sed -i 's/#SetEnv TRUSTED_PROXIES ""/SetEnv TRUSTED_PROXIES "varnish"/' .ddev/apache/apache-site.conf ddev restart ``` The Varnish server acts as the application’s primary entry point. If you run `ddev describe`, you can see that Varnish is now the one responding to DDEV domain `.ddev.site` while the web server still replies to `127.0.0.1` with its own ports. You can see Varnish headers in HTTP responses, for example: ``` % curl -s -c cookies.txt -b cookies.txt -I https://.ddev.site:/ HTTP/2 200 server: Apache/2.4.65 (Debian) vary: Origin,X-Editorial-Mode via: 1.1 varnish (Varnish/7.1) x-cache: HIT x-cache-debug: 1 x-cache-hits: 5 x-cache-ttl: 87654.321 x-debug-token: 012345 x-debug-token-link: https://.ddev.site://_profiler/012345 x-powered-by: Ibexa Commerce v5 x-robots-tag: noindex x-varnish: 12345 67890 xkey: ez-all c52 ct42 l2 pl1 p1 p2 content-length: 45678 ``` You can see how the `web` server is responding to `varnish`: ``` % curl -s -H "Surrogate-Capability: abc=ESI/1.0" http://127.0.0.1:/product-catalog | grep 'esi:include' **Tip: Tip** > > You can use [`jq`](https://jqlang.org/) to format and colorize Elasticsearch REST API outputs. ### Solr The following sequence of commands: 1. Adds the Solr container 2. Sets Solr as the search engine 3. Start the DDEV cluster to creates core config by combining default files and those provided by Ibexa DXP 4. Restarts the DDEV cluster and clears application cache 5. Reindexes the content ``` ddev add-on get ddev/ddev-solr ddev config --web-environment-add SEARCH_ENGINE=solr ddev config --web-environment-add SOLR_DSN=http://solr:8983/solr ddev config --web-environment-add SOLR_CORE=collection1 ddev start mkdir .ddev/solr/configsets/collection1 ddev exec -s solr cp -R /opt/solr/server/solr/configsets/_default/conf/* /mnt/ddev_config/solr/configsets/collection1/ cp -R vendor/ibexa/solr/src/lib/Resources/config/solr/* .ddev/solr/configsets/collection1/ ddev restart ddev php bin/console cache:clear ddev php bin/console ibexa:reindex ``` You can now check whether Solr works. For example, the `ddev exec curl -s http://solr:SolrRocks@solr:8983/api/cores/` command: - checks whether the `web` server can access the `solr` server - checks whether `collection1` exists and its status - displays `collection1`'s `numDocs` that shouldn't be zero if indexing worked correctly You can access the Solr admin UI from the host by: - running `ddev solr-admin` command - accessing port 8983 on the same `.ddev.site` subdomain than the web server (you can use `ddev describe` to get this URL) Use the credentials username `solr` and password `SolrRocks`. For more information on topics such as available versions of Solr, see [ddev/ddev-solr README](https://github.com/ddev/ddev-solr). ## Share cache and sessions You can add a [persistence cache pool](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/persistence_cache/#persistence-cache-configuration) and a [session handler](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/sessions/#session-handlers) to the cluster. In the following examples: - the same service is used to store both persistence cache and sessions - the session handler is set on Symfony side, not on PHP side ### Install Redis or Valkey DDEV supports multiple Redis-compatible implementation, including Redis itself and Valkey. You can switch between them using the `ddev redis-backend ` command after adding the `ddev/ddev-redis` add-on. For example, you can switch to Valkey by running `ddev add-on get ddev/ddev-redis; ddev redis-backend valkey/valkey:9`. For more information, see [Swappable Redis backends](https://github.com/ddev/ddev-redis?tab=readme-ov-file#swappable-redis-backends) in DDEV's `dddev-redis` add-on documentation. The following sequence of commands: 1. Adds the Redis container. 2. Set Redis as the cache pool. 3. Sets Redis as the session handler. 4. Changes `maxmemory-policy` from default `allkeys-lfu` to a [value accepted by the `RedisTagAwareAdapter`](https://github.com/symfony/cache/blob/5.4/Adapter/RedisTagAwareAdapter.php#L95). 5. Restarts the DDEV cluster and clears application cache. ``` ddev add-on get ddev/ddev-redis ddev config --web-environment-add CACHE_POOL=cache.redis ddev config --web-environment-add CACHE_DSN=redis ddev config --web-environment-add SESSION_HANDLER_ID='Ibexa\\Bundle\\Core\\Session\\Handler\\NativeSessionHandler' ddev config --web-environment-add SESSION_SAVE_PATH=tcp://redis:6379 sed -i 's/maxmemory-policy allkeys-lfu/maxmemory-policy volatile-lfu/' .ddev/redis/redis.conf; ddev restart ddev php bin/console cache:clear ``` You can now check whether the data store backend works. For example, the `ddev redis-cli MONITOR` command returns outputs, for example, `"SETEX" "ezp:`, `"MGET" "ezp:`, `"SETEX" "PHPREDIS_SESSION:`, or `"GET" "PHPREDIS_SESSION:`, while navigating into the website, in particular the back office. See [Redis commands](https://redis.io/docs/latest/commands/) for more details such as information about the [`MONITOR`](https://redis.io/docs/latest/commands/monitor/) command used in the previous example. # DevOps ## Cache clearing ### Clearing file cache using the Symfony cache:clear command Symfony provides a command for clearing cache. It deletes all file-based caches, which mainly consist of a Twig template, a [service container](https://doc.ibexa.co/en/latest/api/php_api/php_api/#service-container), and the Symfony route cache, but also everything else stored in the cache folder. Out of the box on a single-server setup this includes Content cache. For further information on the command's use, see its help text: ``` php bin/console --env=prod cache:clear -h ``` > **Note: Note** > > If you don't specify an environment, by default `cache:clear` clears the cache for the `dev` environment. If you want to clear it for `prod` you need to use the `--env=prod` option. > **Caution: Clustering** > > In [clustering](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/clustering/clustering/index.md) setup (with several web servers), the command to clear file cache needs to be executed on every web server. ### Clearing content cache on a cluster setup For a [cluster](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/clustering/clustering/index.md) setup, the content cache ([HTTP cache](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/http_cache/http_cache/index.md) and [Persistence cache](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/persistence_cache/index.md)) must be set up to be shared among the servers. While all relevant cache is cleared for you on repository changes when using the APIs, there might be times where you need to clear cache manually: - Varnish: [Cache purge](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/http_cache/reverse_proxy/#using-varnish-or-fastly) - Persistence Cache: [Using Cache service](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/persistence_cache/#using-cache-service) ## Web Debug Toolbar As of Ibexa DXP v4.5, the [Symfony Web Debug Toolbar](https://symfony.com/doc/7.4/profiler.html) is no longer installed by default. To install it, run the following command: ``` composer require symfony/debug-pack ``` After you have installed Symfony Web Debug Toolbar, it's available when running Ibexa DXP in the `dev` environment. It's extended with some Ibexa DXP-specific information: *[Image: Ibexa DXP info in Web Debug Toolbar]* #### SPI (persistence) This section provides the number of non-cached SPI calls and handlers. You can see details of these calls in the [Symfony Profiler](https://symfony.com/doc/7.4/profiler.html) page. #### SiteAccess Here you can see the name of the current SiteAccess and how it was matched. For reference see the [list of possible SiteAccess matchers](https://doc.ibexa.co/en/latest/multisite/siteaccess/siteaccess_matching/#available-siteaccess-matchers). ## Logging and debug configuration Logging in Ibexa DXP consists of two parts. One are several debug systems that integrate with Symfony developer toolbar to give you detailed information about what is going on. The other is the standard [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) logger, as provided by Symfony using [Monolog](https://github.com/Seldaek/monolog). ### Debugging in dev environment When using the Symfony `dev` [environment](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/environments/index.md), the system tracks additional metrics for you to be able to debug issues. They include Symfony cache use, and a [persistence cache](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/persistence_cache/#persistence-cache-configuration) use. #### Reducing memory use > **Tip: Tip** > > For long-running scripts, see [Long-running console commands](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/performance/#long-running-console-commands). If you're running out of memory and don't need to keep track of cache hits and misses, you can disable persistence cache logging, represented by the setting `parameters.ibexa.spi.persistence.cache.persistenceLogger.enableCallLogging`. In `config_dev.yaml`: ``` parameters: ibexa.spi.persistence.cache.persistenceLogger.enableCallLogging: false ``` ### Error logging and rotation Ibexa DXP uses the [Monolog](https://github.com/Seldaek/monolog) component to log errors, and it has a `RotatingFileHandler` that allows for file rotation. According to [their documentation](https://seldaek.github.io/monolog/doc/02-handlers-formatters-processors.html#log-to-files-and-syslog), it "logs records to a file and creates one logfile per day. It also deletes files older than `$maxFiles`". Monolog's handler can be configured in `config/packages//monolog.yaml`: ``` monolog: handlers: main: type: rotating_file max_files: 10 path: '%kernel.logs_dir%/%kernel.environment%.log' level: debug ``` ### Using `logrotate` Monolog themselves recommend using [`logrotate`](https://manpages.debian.org/jessie/logrotate/logrotate.8.en.html) instead of doing the rotation in the handler, because it gives better performance. # Backup You should always make sure that your solution is properly backed up. The following example shows you how to do this on a Linux-UNIX-based system. You should shut down the DXP if it's running before making a backup. > **Note: Externally stored assets** > > If you store assets in any external service or localization, you should back them up before proceeding. 1. Navigate into the Ibexa DXP directory: ``` cd /path/to/ibexa ``` 2. Clear all caches: ``` rm -rf var/cache/* rm -rf var/share/* rm -rf var/logs/* ``` 3. Create a dump of the database: **MySQL** ``` mysqldump -u --add-drop-table > db_backup.sql ``` **PostgreSQL** ``` pg_dump -c --if-exists > db_backup.sql ``` 4. In parent directory create a tar archive of the files (including the database dump) using the "tar" command: ``` tar cfz backup_of_ibexa.tar.gz ibexa ``` At this point, the file `backup_of_ibexa.tar.gz` should contain a backup of database and files. # Performance Ibexa DXP can be set up to run efficiently on almost any modern configuration. What follows is a list of recommendation that make your installation perform better. > **Note: Note** > > All the following recommendations are valid for both development and production setups, unless otherwise noted. If you're in a hurry, the most important recommendations on this page are: - Dump optimized Composer autoload classmap - Use a full web (Nginx/Apache) server with vhost - Avoid shared filesystems for code (Docker for Mac/Win, VirtualBox/\*, Vagrant, and more), or find ways to optimize or work around the issues. - For clustering (mainly relevant for production/staging), reduce latency to Redis/Valkey, use Varnish and [Solr](https://doc.ibexa.co/en/latest/search/search_engines/solr_search_engine/solr_overview/index.md). ## Client - Always use an up-to-date browser and an up-to-date operating system so you have access to latest browser versions - If possible, use a fast, stable internet connection, because an unreliable connection slows down UI ## Server In production setups: - Always use reverse proxy, and if possible use Varnish. - Compared to the built-in Symfony Proxy in PHP Varnish is much faster and is able to queue up requests for the same fresh/invalidated resource. - With [ibexa/http-cache](https://github.com/ibexa/http-cache) support for xkey and grace Varnish provides more stable performance in read/write scenarios. - Set up Ibexa DXP in [cluster mode](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/clustering/clustering/index.md) if you need to handle bigger spikes of traffic than a single server can manage. - See [recommendation for Redis-compatible data stores](#redis-compatible-data-stores) and [Search](#search) below. > **Note: Note** > > The following recommendations are ordered from largest to smallest impact they have on performance in general. ### VM - Avoid shared filesystems for code (for example, Docker for Mac/Win, VirtualBox/\*, or Vagrant), because they typically slow down the application 10x or more, compared to native Linux filesystem. - VM in itself also adds 10-30% of overhead. However when it comes to production, for example, AWS vs barebones, it also comes down to cost and convenience factors. ### Web server - Use Nginx/Apache even for development, as PHP's built-in web server (as exposed via Symfony's `server:*` commands) is only able to handle one request at a time (including JS/CSS/\* asset loading, and more). - Use a recent version of nginx, set up https, and enable http/2 to reduce connection latency on parallel requests. ### PHP - Always enable opcache for php-fpm/`mod_php`. - Prefer php-fpm and web server using it over fast-cgi for lower overall memory usage. ### Symfony - Review the [Symfony performance documentation](https://symfony.com/doc/7.4/performance.html) and apply matching suggestions, including OPCache configuration if enabled. ### Frontend assets Deploy a [production build](https://webpack.js.org/guides/production/) of your assets to reduce their size and improve loading time. You can build them by running `yarn encore prod`, or by setting the environmental variable `NODE_ENV` to `production` in your production environment. ### Composer - Keep Composer up to date. - Always dump optimized class map using `composer dump-autoload --optimize` or relevant flags on `composer install/update`. ### Redis-compatible data stores - Redis and its alternatives, like Valkey, can in some cases perform better than filesystem cache even with a single server, as it offers better general performance for operations invalidating cache. - However, pure read performance is slower, especially if the next points aren't optimized. - With cache being on different node(s) than web server, make sure to try to tune latency between the two. > **Tip: Tip** > > Check if your cloud provider has native service for Redis, as those might be better tuned. When using Redis or Valkey, make sure to tune it for in-memory cache usage. The persistence feature isn't needed with cache and severely slows down execution time. [For use with sessions](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/sessions/#cluster-setup) however, persistence can be a good fit if you want sessions to survive service interruptions. For more information, see [Redis clustering](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/persistence_cache/#redis-clustering). ### Search - Use [Solr Bundle and Solr](https://doc.ibexa.co/en/latest/search/search_engines/solr_search_engine/solr_overview/index.md) to greatly offload your database and get more stable performance on your installation. ## Long-running console commands Executing long-running console commands can result in running out of memory. Two examples of such commands are a custom import command and the indexing command provided by the [Solr Bundle](https://doc.ibexa.co/en/latest/search/search_engines/solr_search_engine/solr_overview/index.md). ### Reducing memory usage To avoid quickly running out of memory while executing such commands you should make sure to: 1. Always run in prod environment using: `--env=prod` 1. See [Environments](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/environments/index.md) for further information on Symfony environments. 2. See [Logging and debug configuration](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/devops/#logging-and-debug-configuration) for some of the different features enabled in development environments, which by design use memory. 1. For logging using monolog, if you use either the default `fingers_crossed`, or `buffer` handler, make sure to specify `buffer_size` to limit how large the buffer grows before it gets flushed: ``` # config_prod.yaml (partial example) monolog: handlers: main: type: fingers_crossed buffer_size: 200 ``` 1. Run PHP without memory limits: `php -d memory_limit=-1 bin/console ` 1. Disable `xdebug` *(PHP extension to debug/profile php use)* when running the command, this causes php to use much more memory. > **Note: Memory still grows** > > Even when everything is configured like described above, memory grows for each iteration of indexing/inserting a content item with at least *1kb* per iteration after the initial first 100 rounds. This is expected behavior. To be able to handle more iterations you have to do one or several of the following: > > - Change the import/index script in question to [use process forking](#process-forking-with-symfony) to avoid the issue. > - Upgrade PHP: *newer versions of PHP are typically more memory-efficient.* > - Run the console command on a machine with more memory (RAM). ### Process forking with Symfony The recommended way to completely avoid "memory leaks" in PHP in the first place is to use processes. For console scripts this is typically done using process forking which is achievable with Symfony. The things you need to do: 1. Change your command so it supports taking slice parameters, like for instance a batch size and a child-offset parameter. 1. *If defined, child-offset parameter denotes if a process is a child, this can be accomplished using two commands as well.* 2. *If not defined, it's the master process which executes the processes until nothing is left to process.* 1. Change the command so that the master process takes care of forking child processes in slices. 1. For execution in-order, [you may look to our platform installer code](https://github.com/ibexa/core/blob/main/src/bundle/RepositoryInstaller/Command/InstallPlatformCommand.php#L220) used to fork out Solr indexing after installation to avoid cache issues. 2. For parallel execution of the slices, [see Symfony doc for further instruction](https://symfony.com/doc/7.4/components/process.html#process-signals). # Background tasks Some operations in Ibexa DXP don’t have to run immediately when a user clicks a button, for example, re-indexing product prices or processing bulk data. Running such operations in real time could slow down the system and disrupt the user experience. To solve this, Ibexa DXP provides a package called Ibexa Messenger, which is an overlay to [Symfony Messenger](https://symfony.com/doc/7.4/messenger.html), and it's job is to queue tasks and run them in the background. Ibexa DXP sends messages (or commands) that represent the work to be done later. These messages are stored in a queue and picked up by a background worker, which ensures that resource-heavy tasks are executed at a convenient time, without putting excessive load on the system. Ibexa Messenger supports multiple storage backends, such as Doctrine, Redis/Valkey, and PostgreSQL, and gives developers the flexibility to create their own message handlers for custom use cases. ## How it works Ibexa Messenger uses a command bus as a queue that stores messages, or commands, which tell the system what you want to happen, and separates them from the handler, which is the code that actually performs the task. The process works as follows: 1. A message PHP object is dispatched, for example, `ProductPriceReindex`. 2. The message is wrapped in an envelope, which may contain additional metadata, called stamps, for example, `DeduplicateStamp`. 3. The message is placed in the transport queue. It can be a Doctrine table, a Redis/Valkey queue, and so on. 4. A worker process continuously reads messages from the queue, pulls them into the default bus `ibexa.messenger.bus` and assigns them to the right handler. 5. A handler service processes the message (executes the command). You can register multiple handlers for different jobs. Here is an example of how you can extend your code and use Ibexa Messenger to process your tasks: ### Configure package Create a config file, for example, `config/packages/ibexa_messenger.yaml` and define your transport: ``` ibexa_messenger: # The DSN of the transport, as expected by Symfony Messenger transport factory. transport_dsn: 'doctrine://default?table_name=ibexa_messenger_messages&auto_setup=false' deduplication_lock_storage: enabled: true # Doctrine DBAL primary connection or custom service type: doctrine # One of "doctrine"; "custom"; "service" # The service ID of a custom Lock Store, if "service" type is selected service: null # The DSN of the lock store, if "custom" type is selected dsn: null ``` > **Note: Supported transports** > > You can define different transports: Ibexa Messenger has been tested to work with Redis, MySQL, PostgreSQL. For more information, see [Symfony Messenger documentation](https://symfony.com/doc/current/messenger.html#transports-async-queued-messages) or [Symfony Messenger tutorial](https://symfonycasts.com/screencast/messenger/install#installing-messenger). ### Start worker Use a process manager of your choice to run the following command, or make it start together with the server: ``` php bin/console messenger:consume ibexa.messenger.transport --bus=ibexa.messenger.bus --siteaccess=` ``` In [multi-repository setups](https://doc.ibexa.co/en/latest/administration/configuration/repository_configuration/index.md), the worker process always works for a [SiteAccess](https://doc.ibexa.co/en/latest/multisite/multisite_configuration/#siteaccess-configuration) that you indicate by using the `--siteaccess` option, therefore you may need to run multiple workers, one for each SiteAccess. > **Warning: Multi-repository setups** > > Doctrine transport works across multiple repositories without issues, but other transports may need to be adjusted, so that queues across different repositories are not accidentally shared. > **Note: Deploying Ibexa Messenger** > > Additional considerations regarding the deployment of Symfony Messenger to production, which you can find in [Symfony documentation](https://symfony.com/doc/current/messenger.html#deploying-to-production) apply to Ibexa Messenger as well. ### Dispatch message Dispatch a message from your code like in the following example: ``` bus->dispatch($message); // Alternatively, wrap with stamps. In this case, DeduplicateStamp ensures // that if similar command exists in the queue (or is being processed) // it will not be queued again. $envelope = Envelope::wrap( $message, [new DeduplicateStamp('command-name-1')] ); $this->bus->dispatch($envelope); } } ``` ### Register handler Create the handler class: ``` **Tip: Tip** > > See also [Environments in Symfony doc](https://symfony.com/doc/7.4/configuration.html#configuration-environments). ## Web server configuration For example, when you use Apache, in the [`VirtualHost` example](https://raw.githubusercontent.com/ibexa/post-install/main/resources/templates/apache2/vhost.template) in your installation, the required `VirtualHost` configurations have been already included. You can switch to the desired environment by setting the `ENVIRONMENT` variable to `prod`, `dev` or another custom value, like in the following example: ``` # Environment. # Possible values: "prod" and "dev" out-of-the-box, other values possible with proper configuration # Defaults to "prod" if omitted (uses SetEnvIf so value can be used in rewrite rules) SetEnvIf Request_URI ".*" APP_ENV="dev" ``` ## Custom environments If you want to use a custom environment (something other than `prod` and `dev`), you need to place dedicated configuration files in a separate folder: `config/packages//config_.yaml` The name used as `` is the one that can be used as value of the `ENVIRONMENT` variable. This enables you to override settings defined in the main configuration file, depending on your environment (for example database settings). # Sessions Sessions are handled by the Symfony framework, specifically API and underlying session handlers provided by the HttpFoundation component. It's further enhanced in Ibexa DXP with support for SiteAccess-aware session cookie configuration. > **Note: Note** > > Use of Redis, Valkey, or experimentally PDO as session handler is a requirement in a cluster setup, for details [see below](#cluster-setup). For an overview of the clustering feature see [Clustering](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/clustering/clustering/index.md). ## Configuration Symfony offers the possibility to change many session options at application level (for example, in Symfony [`framework` configuration](https://symfony.com/doc/7.4/reference/configuration/framework.html#session)). These options include: - `cookie_domain` - `cookie_path` - `cookie_lifetime` - `cookie_secure` - `cookie_httponly` However, in Ibexa DXP you can set up several sites within one Symfony application, so you can also define session configuration per SiteAccess and SiteAccess group level. ### Session options per SiteAccess All site-related session configuration can be defined per SiteAccess and SiteAccess group under the `ibexa.system..session` [configuration key](https://doc.ibexa.co/en/latest/administration/configuration/configuration/#configuration-files): ``` ibexa: system: my_siteaccess: session: # Default session name is IBX_SESSION_ID{siteaccess_hash} # (unique session name per SiteAccess) name: my_session_name # These are optional.  # If not defined they will fall back to Symfony framework configuration,  # which itself falls back to default php.ini settings cookie_domain: mydomain.com cookie_path: /foo cookie_lifetime: 86400 cookie_secure: false cookie_httponly: true ``` ## Session handlers In Symfony, a session handler is configured with `framework.session.handler_id`. Symfony can be configured to use custom handlers, or fall back to what is configured in PHP by setting it to null (`~`). ### Default configuration Ibexa DXP adapts Symfony's defaults to make sure its session save path is always taken into account: ``` # Default session configuration framework: session: # handler_id can be set to null (~) like default in Symfony, if it so will use default session handler from php.ini # But in order to use %ibexa.session.save_path%, default Ibexa DXP instead sets %ibexa.session.handler_id% to: # - session.handler.native_file (default) # - Ibexa\Bundle\Core\Session\Handler\NativeSessionHandler (recommended value for Cluster usage, using php-redis session handler ) handler_id: '%ibexa.session.handler_id%' ``` ### Recommendations for production setup #### Single-server setup For a single server, the default file handler is preferred. #### Cluster setup See [shared sessions in the clustering guide](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/clustering/clustering/#shared-sessions). ##### Handling sessions with Redis and Valkey Ibexa DXP supports storing sessions with [Redis](https://pecl.php.net/package/redis) or [Valkey](https://valkey.io/) data stores. To set it up, you need to: - [Configure the session save handler settings in `php.ini`](https://github.com/phpredis/phpredis/#php-session-handler) - Set `%ibexa.session.handler_id%` to `~` *(null)* in `config/packages/ibexa.yaml` Alternatively if you have needs to configure the servers dynamically: - Set `%ibexa.session.handler_id%` (or `SESSION_HANDLER_ID` env var) to `Ibexa\Bundle\Core\Session\Handler\NativeSessionHandler` - Set `%ibexa.session.save_path%` (or `SESSION_SAVE_PATH` env var) to [save_path config for Redis](https://github.com/phpredis/phpredis/#php-session-handler) > **Note: Ibexa Cloud** > > For Ibexa Cloud installations, the [`ibexa/cloud` package](https://doc.ibexa.co/en/latest/ibexa_cloud/install_on_ibexa_cloud/index.md) performs configuration based on the `.platform.app.yaml` file. If you're on `php-redis` v4.2.0 and higher, you can optionally tweak [`php-redis` settings](https://github.com/phpredis/phpredis#session-locking) for session locking. Ideally keep [persistence cache](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/persistence_cache/index.md) and session data separated: - Sessions can't risk getting [randomly evicted](https://redis.io/docs/latest/develop/reference/eviction/#eviction-policies) when you run out of memory for cache. - You can't completely disable eviction either, as the data store then starts to refuse new entries once full, including new sessions. - Either way, you should monitor your data store instances and make sure you have enough memory set aside for active sessions/cache items. If you want to make sure sessions survive data store or server restarts, consider setting up [persistent storage](https://redis.io/docs/latest/operate/oss_and_stack/management/persistence/) instance for sessions. ##### Alternative storing sessions in database by using PDO For setups where database is preferred for storing sessions, you may use Symfony's PdoSessionHandler, although it's not currently recommended from performance perspective. Below is a configuration example for Ibexa DXP. Refer to the [Symfony Cookbook](https://symfony.com/doc/7.4/session.html#session-database-pdo) for full documentation. ``` framework: session: # ... handler_id: session.handler.pdo parameters: pdo.db_options: db_table: session db_id_col: session_id db_data_col: session_value db_time_col: session_time services: PDO: arguments: dsn: 'mysql:dbname=' user: password: Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler: arguments: ['@pdo', '%pdo.db_options%'] ``` # Logging ## Sensitive user data Some logs can contain personal information such as User ID or password. By default, Ibexa DXP doesn't log User IDs. You can change this behavior by modifying the following setting: ``` ibexa.commerce.site_access.config.core.default.gdpr.store_user_id_in_logs: false ``` If the email text contains a password that should not be logged in the DB, you have to specify this password as a template parameter. `MailHelperService` replaces the template parameter `password` with `***`. # Development security > **Tip: Tip** > > See [Permissions](https://doc.ibexa.co/en/latest/permissions/permissions/index.md) for information about the permissions system in Ibexa DXP. > **Note: Security checklist** > > See the [Security checklist](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/security/security_checklist/index.md) for a list of security-related issues you should take care of before going live with a project. ## Symfony authentication To use Symfony authentication with Ibexa DXP, use the following configuration (in `config/packages/security.yaml`): ``` security: firewalls: ibexa_front: pattern: ^/ user_checker: Ibexa\Core\MVC\Symfony\Security\UserChecker anonymous: ~ form_login: require_previous_session: false logout: ~ ``` And in `config/routes.yaml`: ``` login: path: /login defaults: { _controller: Ibexa\Core\MVC\Symfony\Controller\SecurityController::loginAction } login_check: path: /login_check logout: path: /logout ``` > **Note: Note** > > You can fully customize the routes and/or the controller used for login. However, remember to match `login_path`, `check_path` and `logout.path` from `security.yaml`. > > See [security configuration reference](https://symfony.com/doc/7.4/reference/configuration/security.html) and [standard login form documentation](https://symfony.com/doc/7.4/security.html#form-login). ### Authentication using Symfony Security component Authentication is provided by the Symfony Security component. [Native and universal `form_login`](https://symfony.com/doc/7.4/security.html#form-login) is used, in conjunction with an extended `DaoAuthenticationProvider` (DAO stands for *Data Access Object*), the `RepositoryAuthenticationProvider`. Native behavior of `DaoAuthenticationProvider` has been preserved, making it possible to still use it for pure Symfony applications. #### Security controller A `SecurityController` is used to manage all security-related actions and is thus used to display the login form. It follows all standards explained in [Symfony security documentation](https://symfony.com/doc/7.4/security.html#form-login). The base template used is [`Security/login.html.twig`](https://github.com/ibexa/core/blob/main/src/bundle/Core/Resources/views/Security/login.html.twig). The layout used by default is `%ibexa.content_view.viewbase_layout%` (empty layout) but can be configured together with the login template: ``` ibexa: system: my_siteaccess: user: layout: layout.html.twig login_template: user/login.html.twig ``` ##### Redirection after login By default, Symfony redirects to the [URI configured in `security.yaml` as `default_target_path`](https://symfony.com/doc/7.4/reference/configuration/security.html). If not set, it defaults to `/`. #### Remember me It's possible to use the "Remember me" functionality. Refer to the [Symfony cookbook on this topic](https://symfony.com/doc/7.4/security/remember_me.html). If you want to use this feature, you must at least extend the login template to add the required checkbox: ``` {% extends "@IbexaCore/Security/login.html.twig" %} {% block login_fields %} {{ parent() }} {% endblock %} ``` #### Login handlers / SSO Symfony provides native support for [multiple user providers](https://symfony.com/doc/7.4/security/user_providers.html). This makes it easy to integrate any kind of login handlers, including SSO and existing third-party bundles (for example, [FR3DLdapBundle](https://github.com/Maks3w/FR3DLdapBundle), [HWIOauthBundle](https://github.com/hwi/HWIOAuthBundle), [FOSUserBundle](https://github.com/FriendsOfSymfony/FOSUserBundle), [BeSimpleSsoAuthBundle](https://github.com/BeSimple/BeSimpleSsoAuthBundle), and more). See [Authenticating a user with multiple user provider](https://doc.ibexa.co/en/latest/users/user_authentication/#authenticate-user-with-multiple-user-providers) for more information. ## JWT authentication To use [JWT authentication](https://www.jwt.io/) with Ibexa DXP, in the provided `config/packages/lexik_jwt_authentication.yaml` file, modify the existing configuration by setting `authorization_header` to `enabled`: ``` lexik_jwt_authentication: secret_key: '%env(APP_SECRET)%' encoder: signature_algorithm: HS256 # Disabled by default, because Page builder uses a custom extractor token_extractors: authorization_header: enabled: true cookie: enabled: false query_parameter: enabled: false ``` You also need a new Symfony firewall configuration for REST and/or GraphQL APIs. It's already provided in `config/packages/security.yaml`, you only need to uncomment it: ``` security: firewalls: ibexa_jwt_rest: request_matcher: Ibexa\Rest\Security\JWTTokenCreationRESTRequestMatcher user_checker: Ibexa\Core\MVC\Symfony\Security\UserChecker stateless: true provider: ibexa json_login: check_path: ibexa.rest.create_token username_path: JWTInput.username password_path: JWTInput.password success_handler: lexik_jwt_authentication.handler.authentication_success failure_handler: lexik_jwt_authentication.handler.authentication_failure ibexa_jwt_rest.api: request_matcher: Ibexa\Rest\Security\AuthorizationHeaderRESTRequestMatcher user_checker: Ibexa\Core\MVC\Symfony\Security\UserChecker provider: ibexa stateless: true jwt: ~ ibexa_jwt_graphql: request_matcher: Ibexa\GraphQL\Security\NonAdminGraphQLRequestMatcher provider: ibexa stateless: true jwt: ~ ``` Finish the setup by generating a [PEM encoded key pair](https://symfony.com/bundles/LexikJWTAuthenticationBundle/2.x/index.html#generate-the-ssl-keys) by using the command: ``` php bin/console lexik:jwt:generate-keypair ``` The generated key pair will be stored in the `config/jwt`directory. > **Note: Ibexa Cloud** > > To generate and store the tokens on Ibexa Cloud, define the `config/jwt` directory as a volume in the `.platform.app.yaml` file. In 3-node cluster setups, ensure that the key pair is the same on all 3 servers. You can use a network share, or use a local mount and manually copy the key pair between the servers. # Security checklist When getting ready to go live with your project for the first time, or when re-launching it, make sure that your setup is secure. > **Caution: Caution** > > Security is an ongoing process. After going live, you should pay attention to security advisories released via [your Service portal](https://support.ibexa.co/), or via [Security advisories](https://developers.ibexa.co/security-advisories) if you're not a subscriber. ## Ibexa DXP ### Carefully select admin users Make sure Admin users and other privileged users who have access to System Information and setup in the back end are vetted and fully trustworthy. As administrator you have access to full information about the system through the `setup/system_info` policy, and also to user data, role editing, and many other critical aspects. The users in your organization who have backend access must be kept up-to-date. Any user leaving the organization must be disabled without delay. If a user takes on a new role in the organization, any required role changes for them in Ibexa DXP must also be made as soon as possible. ### Strong passwords Enforce strong passwords for all users. This is specially important for admin accounts and other privileged users. - Never go online with admin password set to `publish` or any other default value. - Introduce password quality checks. Make sure the checks are strict enough (length/complexity). - 16 characters is a quite secure minimum length. Don't go below 10. - If using Ibexa DXP v4.5 or newer, enable the password rule that rejects any password which has been exposed in a public breach. > **Tip: Password rules** > > See [setting up password rules](https://doc.ibexa.co/en/latest/users/passwords/#password-rules). ### Protect against brute force attacks Consider introducing a measure against brute force login attacks, like CAPTCHA. Adjust timeout limits to your needs: When using the "forgot password" feature, a token is created which expires if the user doesn't click the password reset link that gets mailed to them in time. The time before it expires is set in the parameter `ibexa.site_access.config.default.security.token_interval_spec`. By nature this feature must be available to users before they have logged in, including would-be attackers. If an attacker uses this feature with someone else's email address, the attacker doesn't receive the email. But they could still try to guess the password reset link. That's why this interval should be as short as possible. 5 minutes is often enough. Ibexa DXP allows you to create and send invitations to create an account in the frontend as a customer, the back office as an employee, or the Corporate Portal as a business partner. You can send invitations to individual users or in bulk. These invitations time out according to the parameter `ibexa.site_access.config.default.user_invitation.hash_expiration_time`. This can safely be longer than the "forgot password" time, since attackers cannot generate invitations. Don't leave it longer than it needs to be, though. These timeouts are both entered as [PHP DateInterval duration strings](https://www.php.net/manual/en/dateinterval.construct.php). The forgot password feature defaults to "PT1H" (one hour). The account invitation feature defaults to "P7D" (seven days). ### Disable Varnish when using Fastly If you're using Fastly, disable Varnish. See [Security advisory: EZSA-2020-002](https://developers.ibexa.co/security-advisories/ezsa-2020-002-unauthorised-cache-purge-with-misconfigured-fastly). ### Block upload of unwanted file types The `ibexa.site_access.config.default.io.file_storage.file_type_blacklist` setting is defined in the config file `src/bundle/Core/Resources/config/default_settings.yml` in the Core bundle. It prevents uploading files that might be executed on the server, a Remote Code Execution (RCE) vulnerability. The setting lists filename extensions for files that shouldn't be uploaded. Attempting to upload files from the list results in an error message. There are also other safety measures in place, like using the web server configuration to block execution of uploaded scripts, see the next point. You should adapt this list to your needs. `svg` images are blocked because they may contain JavaScript code. If you opt to allow them, make sure you take steps to mitigate the risk. The default list of blocked file types contains: `hta htm html jar js jse pgif phar php php3 php4 php5 phps phpt pht phtml svg swf xhtm xhtml`. ### Use secure password hashing Use the most secure supported password hashing method. This is currently `bcrypt`, and it's enabled by default. ### Use secure roles and policies Use the following checklist to ensure the roles and policies are secure: - Do roles restrict read/write access to content as they should? Is read/write access to personal data, like User content items, properly restricted? - Are the roles and their use properly differentiated and restricted? Is an editor role used for everyday editorial work? - Is the admin role used only for high-level administrative work? Is the number of people with admin access properly restricted and vetted? - Should people be allowed to create new user accounts themselves? Should such accounts be enabled by default, or require vetting by admins? - Is the role of self-created new users restricted as intended? - Is there a clear role separation between the organisation's internal and external users? - Is access to user data properly restricted, in accordance with GDPR? - Is access to Form Builder uploads managed properly? Files uploaded with the Form Builder are accessible to any user by default. If this doesn't suit you, restrict access to the Form Uploads folder. ### Don't use "hide" for read access restriction The [visibility switcher](https://doc.ibexa.co/en/latest/content_management/locations/#location-visibility) is a convenient feature for withdrawing content from the frontend. It acts as a filter in the frontend by default. You can choose to respect it or ignore it in your code. It isn't permission-based, and doesn't restrict read access to content. Hidden content can be read through other means, like the REST API or GraphQL. If you need to restrict read access to a given content item, you could create a role that grants read access for a given [**Section**](https://doc.ibexa.co/en/latest/administration/content_organization/sections/) or [**Object State**](https://doc.ibexa.co/en/latest/administration/content_organization/object_states/), and set a different section or object State for the given content. Or use other permission-based [**Limitations**](https://doc.ibexa.co/en/latest/permissions/limitations/). ### Minimize exposure Security should be a multi-layered exercise. It's wise to minimize what features you make available to the world, even if there are no known or suspected vulnerabilities in those features, and even if your content is properly protected by roles and policies. Reduce your attack surface by exposing only what you must. - If possible, make the back office unavailable on the open internet. - [Symfony FOSJsRoutingBundle](https://github.com/FriendsOfSymfony/FOSJsRoutingBundle) is required in those releases where it's included, to expose routes to JavaScript. It exposes only the required routes, nothing more. It's only required in the back office SiteAccess though, so you can consider blocking it in other SiteAccesses. You should also go through your own custom routes, and decide for each if you need to expose them or not. See the documentation on [YAML route definitions for exposure](https://github.com/FriendsOfSymfony/FOSJsRoutingBundle/blob/master/Resources/doc/usage.rst#generating-uris). - By default, a [Powered-By header](https://doc.ibexa.co/en/latest/update_and_migration/from_1.x_2.x/update_db_to_2.5/#powered-by-header) is set. It specifies what version of the DXP is running. For example, `x-powered-by: Ibexa Experience v4`. This doesn't expose anything that couldn't be detected through other means. But if you wish to obscure this, you can either omit the version number, or disable the header entirely by setting `enabled: false`. ``` ibexa_system_info: system_info: powered_by: # major => v4 || minor => v4.6 || none release: major # true || false enabled: false ``` - Consider whether certain interfaces must be left available on the open internet. For example: - The `/search` and `/graphql` endpoints - The REST API endpoints > **Tip: Access control** > > One way to lock down an endpoint that should not be openly available is to restrict access to logged-in users, by using the [`access_control`](https://symfony.com/doc/7.4/security/access_control.html) feature. In your YAML configuration, under the `security` key, add an entry similar to the following one, which redirects requests to a login page: > > ``` > security: > access_control: > - { path: ^/search, roles: ROLE_USER} > ``` ### Limit access to Code blocks The [Code block](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/block_reference/#code-block) in Page Builder is designed to accept any HTML, which includes embedded JavaScript. This means that editors who have access to Code blocks could add malicious JS including [cross site scripting (XSS)](https://en.wikipedia.org/wiki/Cross-site_scripting). As site administrator, be aware of this when giving editors access to the Page Builder features, and limit that access only to trusted editors. You can [limit access to specific blocks per content type](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/configure_ct_field_settings/#default-configuration-of-pages) by defining which page blocks are available to editors. ## Symfony ### `APP_SECRET` and other secrets `APP_SECRET` needs to be a strong, random, securely stored value. This applies also to other secrets that may be in use, like the Varnish invalidate token, the JWT passphrase, and any other application-specific secrets. - Don't use a default value like `ff6dc61a329dc96652bb092ec58981f7` or `ThisTokenIsNotSoSecretChangeIt`. - The secret must be secured against unwanted access. Don't commit the value to a version control system. There are several ways of handling it, like with environment variables or files like `.env.local`. Files are considered more secure. If you store the secrets in files, make sure to add those files to `.gitignore` or similar, so they will never be committed to version control systems. - The secret must be long enough. 32 characters is minimum, longer is better. > **Tip: Tip** > > The following command generates a 64-character-long secure random value: > > ``` > php -r "print bin2hex(random_bytes(32));" > ``` > **Note: Note** > > On Ibexa Cloud, if `APP_SECRET` isn't set, the system sets it to [`PLATFORM_PROJECT_ENTROPY`](https://fixed.docs.upsun.com/guides/symfony/environment-variables.html#symfony-environment-variables) ### Symfony production mode Only expose Symfony production mode openly on the internet. Don't expose the dev mode on the internet, otherwise you may disclose things like `phpinfo` and environment variables. For more information about securing Symfony-based systems, see [Authentication and authorisation](https://symfony.com/doc/7.4/security.html), [more on this subject](https://symfony.com/doc/7.4/security.html#learn-more), and [secrets management system](https://symfony.com/doc/7.4/configuration/secrets.html), all from Symfony. ## PHP ### Enable `zend.exception_ignore_args` in PHP 7.4 and newer PHP 7.4 introduced the `zend.exception_ignore_args` setting in `php.ini`. The default value is 0 (disabled) for backwards compatibility. On production sites, this should be set to 1 (enabled) to ensure that stack traces don't include arguments passed to functions. Such arguments could include passwords or other sensitive information. You should also make sure that no stack trace is ever visible to end users of production sites. Visible arguments are unsafe even if the stack traces only show up in log files. ### Disable error output from PHP Symfony in production mode prevents exception messages from being visible to end users. However, if Symfony fails to boot properly, such exceptions may end up being visible, including stack traces. This can be prevented by [disabling error message output in PHP](https://www.php.net/manual/en/language.errors.basics.php). The following `php.ini` configuration values should be used on production sites. When using Ibexa Cloud, the same settings can be configured in Ibexa DXP's `.platform.app.yaml` file. ``` display_errors = Off display_startup_errors = Off ``` ### Other PHP settings Consider what other security related settings are relevant for your needs. The [OWASP PHP Configuration Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/PHP_Configuration_Cheat_Sheet.html) contains several recommendations. For more information, see [PHP's own security manual](https://www.php.net/manual/en/security.php). ## Web server ### Block execution of scripts in `var` directory Make sure that the web server blocks the execution of PHP files and other scripts in the `var` directory. See the line below `# Disable .php(3) and other executable extensions in the var directory` in the example virtual host files for Apache and Nginx, provided in the [installation documentation](https://doc.ibexa.co/en/latest/getting_started/install_ibexa_dxp/#set-up-virtual-host). ### Security headers There are a number of security related HTTP response headers that you can use to improve your security. Headers must be adapted to the site in question, and in most cases it's site owner's responsibility. The headers can be set either by the web server, or by a proxy like Varnish. You can also set headers in PHP code by making a Symfony `RequestListener` for the `kernel.response` event and adding the header to the response object headers list. You most likely need to vary the security headers based on the SiteAccess in question and site implementation details, such as frontend code and libraries used. - `Strict-Transport-Security` - ensures that all requests are sent over HTTPS, with no fallback to HTTP. All production sites should use HTTPS and this header unless they have particular needs. This header is less important during development provided that the site is on an internal, protected network. - `X-Frame-Options` - ensures that the site isn't embedded in a frame by a compliant browser. Set the header to `SAMEORIGIN` to allow embedding by your own site, or `DENY` to block framing completely. - `X-Content-Type-Options` - prevents the browser from second-guessing the mime-type of delivered content. This header is less important if users cannot upload content and/or you trust your editors. However, it's safer to use it at all times. Make sure that the `Content-Type` header is also correctly set, including for the top-level document, to avoid issues with HTML documents being downloaded while they should be rendered. - `Content-Security-Policy` - blocks cross site scripting (XSS) attacks by setting an allowlist (whitelist) of resources to be loaded for a given page. You can set separate lists for scripts, images, fonts, and more. For experimentation and testing, you can use `Content-Security-Policy-Report-Only` before activating the actual policy. - `Referrer-Policy` - limits what information is sent from the previous page or site when navigating to a new page or site. This header has several directives for fine-tuning the referrer information. - `Permissions-Policy` - limits what features the browser can use, such as fullscreen, notifications, location, camera, or microphone. For example, if someone succeeds in injecting their JavaScript into your site, this header prevents them from using those features to attack your users. ### Disable weak cipher suites in TLS Consider blocking the use of TLS 1.2 and older versions. The newer TLS 1.3 doesn't include the weaker cipher suites that are included in 1.2 and older. Removing them means that attackers can't attempt to force other users to use weak ciphers and eavesdrop on their communications. As of December 2024, TLS 1.3 is [supported by ca. 97% of global internet users](https://caniuse.com/tls1-3). If you need to support Internet Explorer or old versions of other browsers, you can disable TLS 1.1 and older, leaving 1.2 and 1.3 enabled. When using Ibexa Cloud, you can [set the minimum TLS version in `.platform/routes.yaml`](https://fixed.docs.upsun.com/define-routes/https.html#enforce-tls-13). ### Enable HTTP Strict Transport Security (HSTS) HSTS forces clients to always communicate with your site over HTTPS. [Most browsers support this](https://caniuse.com/stricttransportsecurity), and there is no downside for browsers that don't. Read the requirements and instructions at [hstspreload.org](https://hstspreload.org/) before you enable HSTS. Make sure to also include subdomains by means of the `includeSubDomains` setting. When using Ibexa Cloud, you can [configure HSTS in `.platform/routes.yaml`](https://fixed.docs.upsun.com/define-routes/https.html#enable-http-strict-transport-security-hsts). Beware if you are using a Varnish proxy: Your version of Varnish may not support HTTPS connections with your web server. If so, make sure to only enable HSTS between your public-facing proxy and the clients. When using Ibexa Cloud, this is handled automatically. ## Domain ### Enable Domain Name System Security Extensions (DNSSEC) DNSSEC is a DNS feature that authenticates responses to DNS requests. It protects against DNS poisoning attacks, which is when an attacker manipulates the responses to DNS requests with the goal of directing users to an IP address the attacker controls. Enabling DNSSEC involves creating the DNSSEC records in your domain, activating DNSSEC with your domain registrar, and enabling DNSSEC signature validation on all DNS servers. [Read more on DNSSEC on ICANN's website](https://www.icann.org/resources/pages/dnssec-what-is-it-why-important-2019-03-05-en). ### Enable domain update/delete protection Domain update/delete protection is a DNS setting that makes it harder for an attacker to take over a domain from the real owner, or hinder availability for users. You can enable this protection at your domain registrar's site. Log in to their site to enable these protection settings and save the new configuration. ### Enable Certificate Authority Authorization (CAA) CAA allows domain owners to specify which Certificate Authorities (CAs) are permitted to issue SSL/TLS certificates for their domain. This prevents attackers from having certificates issued for domains they don't own, hindering some types of attack. CAA is configured in your DNS zone file. ## Database ### Use UTF8MB4 with MySQL/MariaDB If you're using MySQL/MariaDB, use the UTF8MB4 database character set and related collation. The older UTF8 can lead to truncation with 4-byte characters, like some emoji, which may have unpredictable side effects. See [Change from UTF8 to UTF8MB4](https://doc.ibexa.co/en/latest/update_and_migration/from_1.x_2.x/update_db_to_2.5/#change-from-utf8-to-utf8mb4). ### Secure access Secure the database access with strong passwords, keys, firewall, encryption in transit, encryption at rest, and so on, as needed. When using Ibexa Cloud, the provider handles this. ## Underlying stack To avoid exposing your application to any DDOS vulnerabilities or other yet unknown security threats, make sure that you do the following: - Avoid exposing servers on the open internet when not strictly required. - Ensure any servers, services, ports, and virtual hosts that were opened for testing purposes are shut down before going live. - Ensure file system permissions are set up in such a way that the web server or PHP user can't access files they shouldn't be able to read. Those steps aren't needed when using Ibexa Cloud, where the provider handles them. ### Track dependencies - Run servers on a recent operating system and install security patches for dependencies. - Configure servers to alert you about security updates from vendors. Pay special attention to dependencies used by your project directly, or by PHP. The provider of the operating system usually has a service for this. - Enable [GitHub Dependabot](https://docs.github.com/en/code-security/dependabot/dependabot-security-updates/about-dependabot-security-updates) to receive notifications when a security fix is released in a GitHub-hosted dependency. - If you're not using GitHub for your project, you can create a dummy project on GitHub with the same dependencies as your real project, and enable Dependabot notifications for that. - Ensure you get notifications about security fixes in JavaScript dependencies. ### Monitor logs - Enable logging for Ibexa DXP, the web server, any frontend proxies, and the database. - Monitor the logs for unusual and suspicious activity. Consider using log monitoring software to make this easier. - Consider using different accounts for manual administrative tasks and for the day-to-day running of your installation. You could for instance configure Ibexa DXP to use a different database user than the one you use during upgrades. This can make it easier to filter out noise in your log monitoring solution. # Reporting security issues in Ibexa products The security of Ibexa software is a primary concern and is taken seriously. For more information on security in Ibexa products, see [Ibexa Security Policy](https://www.ibexa.co/software-information/security). No engineering team is perfect though, and if you do discover a security issue in one of our products we are very grateful for your help in reporting it to us privately, and refraining from public disclosure until we have found the solution and distributed it. Thank you! ## Channels - If you're a customer or partner, please log in to your Service portal at , click "New Ticket", and report the issue as you would report a normal support request. Ibexa Product Support will respond, take care of the report, and keep you informed of the developments. - It's also possible to report security issues by email to [security@ibexa.co](mailto:security@ibexa.co) - this requires no account. ## Verbosity Please be verbose when reporting issues. The issue will be solved faster if you include: - A **title** describing the gist of the issue in one sentence - A **description** which includes the steps you take to produce the problem, what you expect the result to be, and what actually happens. - Make it clear **why you consider it a security issue**. If you know, also include its type of security issue (example: SQL injection, CSRF, Role/Policy failure), its nature (example: slowing/stopping a web site, leaking sensitive information, destroying data, privilege escalation), and how easy it is to exploit (example: Does it require editor login?). ## Dialogue The engineering team may need your help to clarify certain specifics, so please respond to such inquiries. We keep you updated about the progress on our end and may invite you as collaborators on GitHub to make communication easier. ## Responsible disclosure Please give the engineering team time to produce and distribute a solution before you disclose the issue on other channels, if you plan to do so. Please discuss the specifics with the team. ## Attribution If you want, we can include your name and/or the name of your organisation, a link, and short description about you in the security notification we send out with the fix. Thank you! # Support and maintenance FAQ This page contains answers to most common questions and tips around support and maintenance, references to important parts of the documentation, and tools for developers in their daily work. #### What information should I specify when creating a Customer Support ticket? When reporting a problem to Customer Support the most important information is the version of Ibexa DXP which is used in the project. The best way to specify it's to provide the list of currently installed packages by running: ``` composer show ibexa/* ``` Besides that, all the configuration from the `config` directory may be helpful. You should also list the steps to reproduce the issue, or at least provide a clear description of the circumstances under which the problem occurred. If you stumble upon a database-related problem, providing corresponding logs is also an important step. Additionally, mention recent changes, performed migrations or external scripts/code customizations related to the code which generates the problem. #### What are the recommended ways to increase my project's performance? The most important clues around increasing overall performance of your Ibexa DXP-based project can be found in [the Performance documentation page](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/performance/index.md). #### How can I translate my back office? The language of the back office is based on the browser language. To change it you should install the proper package for your language (see [language packages list](https://github.com/ezplatform-i18n)). Once you have language packages installed, you can switch the language of the back office in the **User Settings** menu. If you don't have a language defined in the browser, it's selected based on the `parameters.locale_fallback` parameter located in `config/packages/ibexa.yaml`. To read more about language managing in Ibexa DXP, see the following doc pages: - [Back office languages](https://doc.ibexa.co/en/latest/multisite/languages/back_office_translations/index.md) - [Multi-language SiteAccesses and corresponding translations](https://doc.ibexa.co/en/latest/multisite/set_up_translation_siteaccess/index.md) #### How can I apply patches to the installation? The easiest way to apply a patch to your project is by using the Unix [`patch`](https://man7.org/linux/man-pages/man1/patch.1.html) command. Remember to clear the cache afterwards. As an alternative to manually applying the patch, you can use [composer-patches](https://github.com/cweagans/composer-patches). You can apply patches received from the Support, community or the others by using your `composer.json` file. For checking the versions you're on, refer to your `composer.lock`. All you need is to specify which package receives patches and give the path/URL to the actual file. This should be done inside the `extra` section. Packages which should receive patches are removed during `composer update` or `composer require` so they can be re-installed and re-patched. When updating to the release that already contains specified patches, Composer throws an error alongside a message that they cannot be applied and are skipped ([this is configurable with 1.x](https://github.com/cweagans/composer-patches/tree/1.x#error-handling)). They can be manually removed from `composer.json` now. #### How to clear the cache properly? Clearing cache is covered by our [documentation](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/devops/#cache-clearing), it applies to file and content (HTTP/persistence) cache. Useful commands: - clearing Symfony cache ``` php bin/console cache:clear --env prod ``` - clearing Redis/Valkey cache ``` php bin/console cache:pool:clear cache.redis ``` - clearing the Symfony cache manually ``` rm -rf var/cache/* rm -rf var/share/* ``` > **Caution: Clearing cache manually** > > Manual cache clearing should be executed with caution, as it doesn't warm up the cache. It results in a significant performance drop on first request, so it shouldn't be called on a production environment. Besides, it could lead to issues with file ownership after running `cache:clear` as a root. #### Where should I place my configuration files? To avoid merge conflicts on important configuration settings during upgrades, moving as much as possible of your configuration to your own files can be a good idea. All project-specific parameters should be kept in separate files. For example, configuration for Page Blocks could be placed in `config/packages/landing_page_blocks.yaml`. You can also place it in `config/landing_page_blocks.yaml`, which should be imported in `config/ibexa.yaml`: ``` imports: - { resource: ../landing_page_blocks.yaml } ``` #### How can I implement authentication in an Ibexa DXP-based project? The best approach is to use Symfony authentication. Check [development security](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/security/development_security/index.md) page for more detailed instructions. # Update and migration # Updating Ibexa DXP To update Ibexa DXP to a newer version, select the version you're currently using: **I am using v1.13 or v2.x** If you have a v1.13 installation, or a v2.x installation lower than the latest v2.5, [update to the v2.5 LTS](https://doc.ibexa.co/en/latest/update_and_migration/from_1.x_2.x/update_from_1.x_2.x/index.md). Afterwards, it's strongly recommended to [update to the v3.3 LTS](https://doc.ibexa.co/en/latest/update_and_migration/from_2.5/update_from_2.5/index.md), then [update to v4.6 LTS](https://doc.ibexa.co/en/latest/update_and_migration/from_4.5/update_from_4.5/index.md) (through [v4.0](https://doc.ibexa.co/en/latest/update_and_migration/from_3.3/to_4.0/index.md) up to v4.6), and finally [to the lastest v5.0 LTS](https://doc.ibexa.co/en/latest/update_and_migration/from_4.6/update_to_5.0/index.md). **I am using v2.5** If you have a v2.5 installation, [update to the v3.3 LTS](https://doc.ibexa.co/en/latest/update_and_migration/from_2.5/update_from_2.5/index.md). Afterwards, it's strongly recommended to also [update to v4.6 LTS](https://doc.ibexa.co/en/latest/update_and_migration/from_4.5/update_from_4.5/index.md) (through [v4.0](https://doc.ibexa.co/en/latest/update_and_migration/from_3.3/to_4.0/index.md) up to v4.6), and finally [to the lastest v5.0 LTS](https://doc.ibexa.co/en/latest/update_and_migration/from_4.6/update_to_5.0/index.md). **I am using v3.3** If you already have a v3.3 installation, [update to the latest v3.3 version](https://doc.ibexa.co/en/latest/update_and_migration/from_3.3/update_from_3.3/index.md). Afterwards, it's strongly recommended to also [update to v4.6 LTS](https://doc.ibexa.co/en/latest/update_and_migration/from_4.5/update_from_4.5/index.md) (through [v4.0](https://doc.ibexa.co/en/latest/update_and_migration/from_3.3/to_4.0/index.md) up to v4.6), and finally [to the lastest v5.0 LTS](https://doc.ibexa.co/en/latest/update_and_migration/from_4.6/update_to_5.0/index.md). **I am using v4.x** - If you have a v4.x installation prior to v4.6, [update to the v4.6 LTS](https://doc.ibexa.co/en/latest/update_and_migration/from_4.5/update_from_4.5/index.md). - If you have a v4.6 installation, [update to the latest patch v4.6.29](https://doc.ibexa.co/en/latest/update_and_migration/from_4.6/update_from_4.6/index.md). Afterwards, it's strongly recommended to also [update to the lastest v5.0 LTS](https://doc.ibexa.co/en/latest/update_and_migration/from_4.6/update_to_5.0/index.md). **I am using v5.0** Refer to the [v5.0 update page](https://doc.ibexa.co/en/latest/update_and_migration/from_5.0/update_from_5.0/index.md) to make sure you're staying up to date. # From 1.13 and 2.x This update procedure applies if you're using: - v1.13 - v2.x - v2.5 lower than the latest v2.5.x Go through the following steps to update to the latest v2.5 LTS (v2.5.30). 1. [Check out a version](https://doc.ibexa.co/en/latest/update_and_migration/from_1.x_2.x/update_app_to_2.5/#1-check-out-a-version) 2. [Resolve conflicts](https://doc.ibexa.co/en/latest/update_and_migration/from_1.x_2.x/update_app_to_2.5/#2-resolve-conflicts) 3. [Update the app](https://doc.ibexa.co/en/latest/update_and_migration/from_1.x_2.x/update_app_to_2.5/#3-update-the-app) 4. [Update the database](https://doc.ibexa.co/en/latest/update_and_migration/from_1.x_2.x/update_db_to_2.5/#4-update-the-database) 5. [Finish the update](https://doc.ibexa.co/en/latest/update_and_migration/from_1.x_2.x/update_db_to_2.5/#5-finish-the-update) # Update app to v2.5 ## 1. Check out a version ### A. Create branch Create a new branch for handling update changes from the branch you're updating on: ``` git checkout -b update-2.5 ``` This creates a new project branch (`update-2.5`) for the update based on your current project branch. ### B. Add `upstream` remote If it's not added as a remote yet, add an `upstream` remote: **ezplatform** ``` git remote add upstream http://github.com/ezsystems/ezplatform.git ``` **ezplatform-ee** ``` git remote add upstream http://github.com/ezsystems/ezplatform-ee.git ``` **ezcommerce** ``` git remote add upstream http://github.com/ezsystems/ezcommerce.git ``` ### C. Prepare for pulling changes Adding `sort-packages` option when updating from \<=v1.13.4, v2.2.3, v2.3.2 Composer sorts packages listed in `composer.json`. If your packages aren't sorted yet, you should prepare for this update to make it clearer which changes you introduce. Assuming you have installed packages on your installation (`composer install`), do the following steps: 1. Add [sort-packages](https://getcomposer.org/doc/06-config.md#sort-packages) to the `config` section in `composer.json`. ``` "config": { "bin-dir": "bin", "sort-packages": true, "preferred-install": { "ezsystems/*": "dist" } }, ``` 2. Use `composer require` to get Composer to sort your packages. The following example updates a few requirements with what you can expect in the upcoming change: ``` composer require --no-scripts --no-update doctrine/doctrine-bundle:^1.9.1 composer require --dev --no-scripts --no-update behat/behat:^3.5.0 # The upcoming change also moves security-advisories to dev as advised by the package itself composer require --dev --no-scripts --no-update roave/security-advisories:dev-master ``` 3. Check that you can install/update packages. ``` composer update ``` If Composer says there were no updates, or if it updates packages without stopping with conflicts, your preparation was successful. 4. Save your work. ``` git commit -am "Sort my existing composer packages in anticipation of update with sorted merge" ``` ### D. Pull the tag into your branch Pull the latest v2.5 tag into the `update-2.5` branch with the following command: ``` git pull upstream v2.5.30 ``` At this stage you may get conflicts, which are a normal part of the update procedure. ## 2. Resolve conflicts ### A. Resolve conflicts If you get a lot of conflicts and you installed from the [support.ez.no / support.ibexa.co](https://support.ibexa.co) tarball or from [ezplatform.com](https://ezplatform.com), you may have incomplete history. To load the full history, run `git fetch upstream --unshallow` from the `update-2.5` branch, and run the merge again. Ignore the conflicts in `composer.lock`, because this file is regenerated when you execute `composer update` later. It's easiest to check out the version of `composer.lock` from the tag and add it to the changes: ``` git checkout --theirs composer.lock && git add composer.lock ``` If you don't keep a copy of `composer.lock` in the branch, you may also remove it by running: ``` git rm composer.lock ``` ### B. Resolve conflicts in `composer.json` You need to fix conflicts in `composer.json` manually. If you're not familiar with the diff output, you may check out the tag's version from the `update-2.5` branch and inspect the changes. ``` git checkout --theirs composer.json && git diff HEAD composer.json ``` This command shows the differences between the target `composer.json` and your own in the diff output. Updating `composer.json` changes the requirements for all of the `ezsystems` / `ibexa` packages. Keep those changes. The other changes remove what you added for your own project. Use `git checkout -p` to selectively cancel those changes (and retain your additions): ``` git checkout -p composer.json ``` Answer `no` (don't discard) to the requirement changes of `ezsystems` / `ibexa` dependencies. Answer `yes` (discard) to removals of your changes. After you're done, inspect the file (you can use an editor or run `git diff composer.json`). You may also test the file with `composer validate`, and test the dependencies by running `composer update --dry-run` (it outputs what it would do to the dependencies, without applying the changes). When finished, run `git add composer.json` and commit. ### C. Fix other conflicts Depending on the local changes you have done, you may get other conflicts, for example, on configuration files or kernel. For each change, edit the file, identify the conflicting changes, and resolve the conflict. Run `git add ` to add the changes. ## 3. Update the app If `EzSystemsPlatformEEAssetsBundle` is present in `app/AppKernel.php`, disable it by removing the `new EzSystems\PlatformEEAssetsBundle\EzSystemsPlatformEEAssetsBundle(),` entry. Since v2.5 eZ Platform uses [Webpack Encore](https://symfony.com/doc/7.4/frontend.html#webpack-encore) for asset management. You need to install [Node.js](https://nodejs.org/en) and [Yarn](https://classic.yarnpkg.com/en/docs/install) to update to this version. In v2.5 it's still possible to use Assetic, like in earlier versions. However, if you're using the latest Bootstrap version, [`scssphp`](https://github.com/leafo/scssphp) doesn't compile correctly with Assetic. In this case, use Webpack Encore. For more information, see [Importing assets from a bundle](https://doc.ibexa.co/en/latest/administration/back_office/back_office_elements/importing_assets_from_bundle/index.md). If you experience issues during the update, see [Troubleshooting](https://doc.ibexa.co/en/latest/getting_started/troubleshooting/#cloning-failed-using-an-ssh-key). ### Run composer update At this point, you should have a `composer.json` file with the correct requirements and you can update dependencies. If you want to first test how the update proceeds without actually updating any packages, you can try the command with the `--dry-run` switch: ``` composer update --dry-run ``` Then, run `composer update` to update the dependencies. ``` composer update ``` ## Next steps Now, proceed to the next step, [updating the database to v2.5](https://doc.ibexa.co/en/latest/update_and_migration/from_1.x_2.x/update_db_to_2.5/index.md). # Update database to v2.5 ## 4. Update the database Before you start this procedure, make sure you have completed the previous step, [Updating the app to v2.5](https://doc.ibexa.co/en/latest/update_and_migration/from_1.x_2.x/update_app_to_2.5/index.md). > **Caution: Caution** > > Always back up your data before running any database update scripts. > > After updating the database, clear the cache. > > Don't use `--force` argument for `mysql` / `psql` commands when performing update queries. If there is any problem during the update, it's best if the query fails immediately, so you can fix the underlying problem before you execute the update again. If you leave this for later you risk ending up with an incompatible database, though the problems might not surface immediately. > **Note: Note** > > If you're starting from version v2.2 or later, skip to the relevant section. ### A. Update to v2.2 #### Change from UTF8 to UTF8MB4 In v2.2 the character set for MySQL/MariaDB database tables changes from `utf8` to `utf8mb4` to support 4-byte characters. To apply this change, use the following database update script: ``` mysql -u -p < vendor/ezsystems/ezpublish-kernel/data/update/mysql/dbupdate-7.1.0-to-7.2.0.sql ``` If you use DFS Cluster, also execute the following database update script: ``` mysql -u -p < vendor/ezsystems/ezpublish-kernel/data/update/mysql/dbupdate-7.1.0-to-7.2.0-dfs.sql ``` Be aware that these upgrade statements may fail due to index collisions. This is because the indexes have been shortened, so duplicates may occur. If that happens, you must remove the duplicates manually, and then repeat the statements that failed. After successfully running those statements, change the character set and collation for each table, as described in [kernel upgrade documentation](https://github.com/ezsystems/ezpublish-kernel/blob/7.5/doc/upgrade/7.2.md). You should also change the character set that is specified in the application config: In `app/config/config.yml`, set the following: ``` doctrine: dbal: connections: default: charset: utf8mb4 ``` Also make the corresponding change in `app/config/dfs/dfs.yml`. #### Migrate landing pages To update to v2.2 with existing landing pages, you need to use a dedicated script. The script is contained in the `ezplatform-page-migration` bundle and **works since version v2.2.2**. To use the script: 1. Run `composer require ezsystems/ezplatform-page-migration` 2. Add the bundle to `app/AppKernel.php`: `new EzSystems\EzPlatformPageMigrationBundle\EzPlatformPageMigrationBundle(),` 3. Run command `bin/console ezplatform:page:migrate` > **Tip: Tip** > > This script uses the layout defined in your landing page. To migrate successfully, you need to copy your zone configuration from `ez_systems_landing_page_field_type` under `ezplatform_page_fieldtype` in the new config. Otherwise the script encounters errors. You can remove the bundle after the migration is complete. The `ezplatform:page:migrate` command migrates landing pages created in eZ Platform v1.x, v2.0 and v2.1 to new Pages. The operation is transactional and rolls back in case of errors. > **Caution: Avoid exception when migrating from eZ Publish** > > If you [migrated to v1.13 from eZ Publish](https://doc.ibexa.co/en/latest/update_and_migration/migrate_to_ibexa_dxp/migrating_from_ez_publish/index.md), and want to upgrade to v2.5, an exception occurs when you run the `bin/console ezplatform:page:migrate` command and the database contains internal drafts of landing pages. > > To avoid this exception, you must first [remove all internal drafts before you migrate](https://doc.ibexa.co/en/latest/update_and_migration/migrate_to_ibexa_dxp/migrating_from_ez_publish/#migration_exception). ##### Block migration In v2.2 Page Builder doesn't offer all blocks that landing page editor did. The removed blocks include Keyword, Schedule, and Form blocks. The Places block has been removed from the clean installation and is only available in the demo out of the box. If you use this block in your site, re-apply its configuration based on the [demo](https://github.com/ezsystems/ezplatform-ee-demo/blob/v2.2.2/app/config/blocks.yml). Later versions of Page Builder come with a Content Scheduler block and new Form Blocks, but migration of Schedule blocks to Content Scheduler blocks and of Form Blocks isn't supported. If there are missing block definitions, such as Form Block or Schedule Block, you have an option to continue, but migrated landing pages come without those blocks. > **Tip: Tip** > > If you use different repositories with different SiteAccesses, use the `--siteaccess` switch to migrate them separately. > **Tip: Tip** > > You can use the `--dry-run` switch to test the migration. After the migration is finished, you need to clear the cache. ###### Migrate layouts The `ez_block::renderBlockAction` controller used in layout templates has been replaced by `EzPlatformPageFieldTypeBundle:Block:render`. This controller has two additional parameters, `locationId` and `languageCode`. Only `languageCode` is required. Also, the HTML class `data-studio-zone` has been replaced with `data-ez-zone-id` See [documentation](https://doc.ibexa.co/en/latest/templating/render_content/render_page/#render-a-layout) for an example on usage of the new controller. ###### Migrate custom blocks Landing page blocks (from v2.1 and earlier) were defined using a class implementing `EzSystems\LandingPageFieldTypeBundle\FieldType\LandingPage\Model\AbstractBlockType`. In Page Builder (from v2.2 onwards), this interface is no longer present. Instead the logic of your block must be implemented in a [Listener](https://doc.ibexa.co/en/latest/content_management/pages/page_blocks/#block-events). Typically, what you previously would do in `getTemplateParameters()`, you now do in the `onBlockPreRender()` event handler. The definition of block parameters has to be moved from `createBlockDefinition()` to the [YAML configuration](https://doc.ibexa.co/en/latest/content_management/pages/create_custom_page_block/index.md) for your custom blocks. For more information about how custom blocks are implemented in Page Builder, see [Creating custom Page blocks](https://doc.ibexa.co/en/latest/content_management/pages/create_custom_page_block/index.md) for your custom blocks. For the migration of blocks from landing page to Page Builder, you need to provide a converter for attributes of custom blocks. For simple blocks you can use `\EzSystems\EzPlatformPageMigration\Converter\AttributeConverter\DefaultConverter`. Custom converters must implement the `\EzSystems\EzPlatformPageMigration\Converter\AttributeConverter\ConverterInterface` interface. `convert()` parses XML `\DOMNode $node` and return an array of `\EzSystems\EzPlatformPageFieldType\FieldType\LandingPage\Model\Attribute` objects. Below is an example of a simple converter for a custom block: ``` app.block.foobar.converter: class: EzSystems\EzPlatformPageMigration\Converter\AttributeConverter\DefaultConverter tags: - { name: ezplatform.fieldtype.ezlandingpage.migration.attribute.converter, block_type: foobar } ``` Notice service tag `ezplatform.fieldtype.ezlandingpage.migration.attribute.converter` that must be used for attribute converters. This converter is only needed when running the `ezplatform:page:migrate` script and can be removed once that has completed. ###### Page migration example Below is an example how to migrate a landing page Layout and Block to new Page Builder. The code is based on the Random block defined in the [Enterprise Beginner tutorial](https://doc.ibexa.co/en/latest/tutorials/page_and_form_tutorial/page_and_form_tutorial/index.md) Landing page code `app/Resources/views/layouts/sidebar.html.twig`: ```
    {% if zones[0].blocks %} {% for block in zones[0].blocks %}
    {{ render_esi(controller('ez_block::renderBlockAction', { 'contentId': contentInfo.id, 'blockId': block.id, 'versionNo': versionInfo.versionNo })) }}
    {% endfor %} {% endif %}
    ``` `app/config/layouts.yml`: ``` ez_systems_landing_page_field_type: layouts: sidebar: identifier: sidebar name: Right sidebar description: Main section with sidebar on the right thumbnail: assets/images/layouts/sidebar.png template: layouts/sidebar.html.twig zones: first: name: First zone second: name: Second zone ``` `src/AppBundle/Block/RandomBlock.php`: ``` locationService = $locationService; $this->contentService = $contentService; $this->searchService = $searchService; } public function getTemplateParameters(BlockValue $blockValue) { $attributes = $blockValue->getAttributes(); $contentInfo = $this->contentService->loadContentInfo($attributes['parentContentId']); $randomContent = $this->getRandomContent( $this->getQuery($contentInfo->mainLocationId) ); return [ 'content' => $randomContent, ]; } /** * Returns random picked Content. * * @param \eZ\Publish\API\Repository\Values\Content\LocationQuery $query * * @return \eZ\Publish\API\Repository\Values\Content\Content */ private function getRandomContent(LocationQuery $query) { $results = $this->searchService->findLocations($query); $searchHits = $results->searchHits; if (count($searchHits) > 0) { shuffle($searchHits); return $this->contentService->loadContentByContentInfo( $searchHits[0]->valueObject->contentInfo ); } return null; } /** * Returns LocationQuery object based on given arguments. * * @param int $parentLocationId * * @return \eZ\Publish\API\Repository\Values\Content\LocationQuery */ private function getQuery($parentLocationId) { $query = new LocationQuery(); $query->query = new Criterion\LogicalAnd([ new Criterion\Visibility(Criterion\Visibility::VISIBLE), new Criterion\ParentLocationId($parentLocationId), ]); return $query; } public function createBlockDefinition() { return new BlockDefinition( 'random', 'Random', 'default', 'assets/images/blocks/random_block.svg', [], [ new BlockAttributeDefinition( 'parentContentId', 'Parent', 'embed', self::PATTERN_CONTENT_ID, 'Choose a valid ContentID', true, false, [], [] ), ] ); } public function checkAttributesStructure(array $attributes) { if (!isset($attributes['parentContentId']) || preg_match(self::PATTERN_CONTENT_ID, $attributes['parentContentId']) !== 1) { throw new InvalidBlockAttributeException('Parent container', 'parentContentId', 'Parent ContentID must be defined.'); } } } ``` `src/AppBundle/DependencyInjection/AppExtension.php`: ``` load('services.yml'); } public function prepend(ContainerBuilder $container) { $configFile = __DIR__ . '/../Resources/config/blocks.yml'; $config = Yaml::parse(file_get_contents($configFile)); $container->prependExtensionConfig('ez_systems_landing_page_field_type', $config); $container->addResource(new FileResource($configFile)); } } ``` `src/AppBundle/Resources/config/blocks.yml`: ``` blocks: random: views: random: template: AppBundle:blocks:random.html.twig name: Random Content Block View ``` `src/AppBundle/Resources/config/services.yml`: ``` services: app.block.random: class: AppBundle\Block\RandomBlock arguments: - '@ezpublish.api.service.location' - '@ezpublish.api.service.content' - '@ezpublish.api.service.search' tags: - { name: landing_page_field_type.block_type, alias: random } ``` Corresponding Page Builder code `app/Resources/views/layouts/sidebar.html.twig`: ```
    {% if zones[0].blocks %} {% set locationId = parameters.location is not null ? parameters.location.id : contentInfo.mainLocationId %} {% for block in zones[0].blocks %}
    {{ render_esi(controller('EzPlatformPageFieldTypeBundle:Block:render', { 'locationId': locationId, 'contentId': contentInfo.id, 'blockId': block.id, 'versionNo': versionInfo.versionNo, 'languageCode': field.languageCode })) }}
    {% endfor %} {% endif %}
    ``` `app/config/layouts.yml`: ``` ezplatform_page_fieldtype: layouts: sidebar: identifier: sidebar name: Right sidebar description: Main section with sidebar on the right thumbnail: assets/images/layouts/sidebar.png template: layouts/sidebar.html.twig zones: first: name: First zone second: name: Second zone ``` `src/AppBundle/Block/Event/Listener/RandomBlockListener.php` in place of `src/AppBundle/Block/RandomBlock.php`: ``` contentService = $contentService; $this->locationService = $locationService; $this->searchService = $searchService; } /** * @return array The event names to listen to */ public static function getSubscribedEvents() { return [ BlockRenderEvents::getBlockPreRenderEventName('random') => 'onBlockPreRender', ]; } /** * @param \EzSystems\EzPlatformPageFieldType\FieldType\Page\Block\Renderer\Event\PreRenderEvent $event * * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException */ public function onBlockPreRender(PreRenderEvent $event) { //BlockDefinitionFactory $blockValue = $event->getBlockValue(); $renderRequest = $event->getRenderRequest(); $contentInfo = $this->contentService->loadContentInfo($blockValue->getAttribute('parentContentId')->getValue()); $randomContent = $this->getRandomContent( $this->getQuery($contentInfo->mainLocationId) ); $parameters = $renderRequest->getParameters(); $parameters['content'] = $randomContent; $renderRequest->setParameters($parameters); } /** * Returns random picked Content. * * @param \eZ\Publish\API\Repository\Values\Content\LocationQuery $query * * @return \eZ\Publish\API\Repository\Values\Content\Content * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException */ private function getRandomContent(LocationQuery $query) { $results = $this->searchService->findLocations($query); $searchHits = $results->searchHits; if (count($searchHits) > 0) { shuffle($searchHits); return $this->contentService->loadContentByContentInfo( $searchHits[0]->valueObject->contentInfo ); } return null; } /** * Returns LocationQuery object based on given arguments. * * @param int $parentLocationId * * @return \eZ\Publish\API\Repository\Values\Content\LocationQuery */ private function getQuery($parentLocationId) { $query = new LocationQuery(); $query->query = new Criterion\LogicalAnd([ new Criterion\Visibility(Criterion\Visibility::VISIBLE), new Criterion\ParentLocationId($parentLocationId), ]); return $query; } } ``` `src/AppBundle/DependencyInjection/AppExtension.php`: ``` load('services.yml'); } public function prepend(ContainerBuilder $container) { $configFile = __DIR__ . '/../Resources/config/blocks.yml'; $config = Yaml::parse(file_get_contents($configFile)); $container->prependExtensionConfig('ezplatform_page_fieldtype', $config); $container->addResource(new FileResource($configFile)); } } ``` `src/AppBundle/Resources/config/blocks.yml`: ``` blocks: random: name: Random category: default thumbnail: assets/images/layouts/sidebar.png #configuration_template: blocks/random_config.html.twig views: random: template: AppBundle:blocks:random.html.twig name: Random Content Block View attributes: parentContentId: type: embed name: Parent Location ID validators: not_blank: message: Please provide parent node ``` `src/AppBundle/Resources/config/services.yml`: ``` services: _defaults: autowire: true autoconfigure: true public: false AppBundle\Block\Event\Listener\RandomBlockListener: ~ app.block.random.converter: class: EzSystems\EzPlatformPageMigration\Converter\AttributeConverter\DefaultConverter tags: - { name: ezplatform.fieldtype.ezlandingpage.migration.attribute.converter, block_type: random } ``` ### B. Update to v2.3 #### Database update script Apply the following database update script: ``` mysql -u -p < vendor/ezsystems/ezpublish-kernel/data/update/mysql/dbupdate-7.2.0-to-7.3.0.sql ``` #### Form Builder In an Enterprise installation, to create the *Forms* container under the content tree root use the following command: ``` php bin/console ezplatform:form-builder:create-forms-container ``` You can also specify content type, Field values and language code of the container, for example: ``` php bin/console ezplatform:form-builder:create-forms-container --content-type custom --field title --value 'My Forms' --field description --value 'Custom container for the forms' --language-code eng-US ``` You also need to run a script to add database tables for the Form Builder. You can find it in > **Caution: Form (ezform) field type** > > After the update, to create forms, you have to add a new content type (for example, named "Form") that contains `Form` field (this content type can contain other fields as well). After that you can use forms inside landing pages via Embed block. ### C. Update to v2.4 #### Workflow When updating an Enterprise installation, you need to [run a script](https://github.com/ezsystems/ezplatform-ee-installer/blob/2.4/Resources/sql/schema.sql#L198) to add database tables for the Editorial Workflow. #### Changes to the Forms folder The built-in Forms folder is located in the Form Section in versions 2.4+. If you're updating your Enterprise installation, you need to add this Section manually and move the folder to it. To allow anonymous users to access Forms, you also need to add the `content/read` policy with the *Form* Section to the Anonymous User. #### Changes to custom tags v2.4 changed the way of configuring custom tags. They're no longer configured under the `ezpublish` key, but one level higher in the YAML structure: ``` ezpublish: system: : fieldtypes: ezrichtext: custom_tags: [exampletag] ezrichtext: custom_tags: exampletag: # ... ``` The old configuration is deprecated, so if you use custom tags, you need to modify your config accordingly. ### D. Update to v2.5 #### Database update script Apply the following database update script: ``` mysql -u -p < vendor/ezsystems/ezpublish-kernel/data/update/mysql/dbupdate-7.4.0-to-7.5.0.sql ``` ##### v2.5.3 To update to v2.5.3, additionally run the following script: ``` mysql -u -p < vendor/ezsystems/ezpublish-kernel/data/update/mysql/dbupdate-7.5.2-to-7.5.3.sql ``` ##### v2.5.6 To update to v2.5.6, additionally run the following script: ``` mysql -u -p < vendor/ezsystems/ezpublish-kernel/data/update/mysql/dbupdate-7.5.4-to-7.5.5.sql ``` or for PostgreSQL: ``` psql < vendor/ezsystems/ezpublish-kernel/data/update/postgres/dbupdate-7.5.4-to-7.5.5.sql ``` ##### v2.5.9 To update to v2.5.9, additionally run the following script: ``` mysql -u -p < vendor/ezsystems/ezpublish-kernel/data/update/mysql/dbupdate-7.5.6-to-7.5.7.sql ``` or for PostgreSQL: ``` psql < vendor/ezsystems/ezpublish-kernel/data/update/postgres/dbupdate-7.5.6-to-7.5.7.sql ``` Additionally, reindex the content: ``` php bin/console ezplatform:reindex ``` #### Changes to database schema The introduction of [support for PostgreSQL](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/databases/#using-postgresql) includes a change in the way database schema is generated. It's now created based on [YAML configuration](https://github.com/ezsystems/ezpublish-kernel/blob/master/eZ/Bundle/EzPublishCoreBundle/Resources/config/storage/legacy/schema.yaml), using the new [`DoctrineSchemaBundle`](https://github.com/ezsystems/doctrine-dbal-schema). If you're updating your application according to the usual procedure, no additional actions are required. However, if you don't update your meta-repository, you need to take two additional steps: - enable `EzSystems\DoctrineSchemaBundle\DoctrineSchemaBundle()` in `AppKernel.php` - add [`ez_doctrine_schema`](https://github.com/ezsystems/ezplatform/blob/2.5/app/config/config.yml#L33) configuration #### Changes to Matrix field type To migrate your content from legacy XML format to a new `ezmatrix` value use the following command: ``` bin/console ezplatform:migrate:legacy_matrix ``` #### Required manual cache clearing if using Redis If you're using Redis as your persistence cache storage you should always clear it manually after an upgrade. You can do it in two ways, by using `redis-cli` and executing the following command: ``` FLUSHALL ``` or by executing the following command: ``` bin/console cache:pool:clear cache.redis ``` #### Updating to 2.5.3 ##### Page Builder This step is only required when updating an Enterprise installation from versions higher than v2.2 and lower than v2.5.3. In case of versions lower than 2.2, skip this step or ignore the information that indexes from a script below already exist. When updating to v2.5.3, you need to run the following SQL commands to add missing indexes: ``` CREATE INDEX ezpage_map_zones_pages_zone_id ON ezpage_map_zones_pages(zone_id); CREATE INDEX ezpage_map_zones_pages_page_id ON ezpage_map_zones_pages(page_id); CREATE INDEX ezpage_map_blocks_zones_block_id ON ezpage_map_blocks_zones(block_id); CREATE INDEX ezpage_map_blocks_zones_zone_id ON ezpage_map_blocks_zones(zone_id); CREATE INDEX ezpage_map_attributes_blocks_attribute_id ON ezpage_map_attributes_blocks(attribute_id); CREATE INDEX ezpage_map_attributes_blocks_block_id ON ezpage_map_attributes_blocks(block_id); CREATE INDEX ezpage_blocks_design_block_id ON ezpage_blocks_design(block_id); CREATE INDEX ezpage_blocks_visibility_block_id ON ezpage_blocks_visibility(block_id); CREATE INDEX ezpage_pages_content_id_version_no ON ezpage_pages(content_id, version_no); ``` #### Updating to 2.5.16 ##### Powered-By header To promote use of eZ Platform, `ezsystems/ez-support-tools` v1.0.10, as of eZ Platform v2.5.16, sets the Powered-By header. It's enabled by default and generates a header like `Powered-By: eZ Platform Enterprise v2`. To omit the version number, use the following configuration: ``` ezplatform_support_tools: system_info: powered_by: release: "none" ``` To opt out of the whole feature, disable it with the following configuration: ``` ezplatform_support_tools: system_info: powered_by: enabled: false ``` #### Updating to v2.5.18 To update to v2.5.18, if you're using MySQL, additionally run the following update SQL command: ``` ALTER TABLE ezpage_attributes MODIFY value LONGTEXT; ``` ##### Update entity managers Version v2.5.18 introduces new entity managers. To ensure that they work in multi-repository setups, you must update the GraphQL schema. You do this manually by following this procedure: 1. Update your project to v2.5.18 and run the `php bin/console cache:clear` command to generate the [service container](https://doc.ibexa.co/en/latest/api/php_api/php_api/#service-container). 1. Run the following command to discover the names of the new entity managers. Take note of the names that you discover: `php bin/console debug:container --parameter=doctrine.entity_managers --format=json | grep ibexa_` 1. For every entity manager prefixed with `ibexa_`, run the following command: `php bin/console doctrine:schema:update --em= --dump-sql` 1. Review the queries and ensure that there are no harmful changes that could affect your data. 1. For every entity manager prefixed with `ibexa_`, run the following command to run queries on the database: `php bin/console doctrine:schema:update --em= --force` ###### VCL configuration for Fastly If you use Fastly, deploy the most up-to-date VCL configuration. Locate the `vendor/ezsystems/ezplatform-http-cache-fastly/fastly/ez_main.vcl` file, make sure that it has been updated with the following changes, and upload it to your Fastly: - Add the following lines: ``` if (req.restarts == 0 && resp.status == 301 && req.http.x-fos-original-url) { set resp.http.location = regsub(resp.http.location, "/_fos_user_context_hash", req.http.x-fos-original-url); } ``` - Move the `#FASTLY recv` macro call to a new location, right after the `Preserve X-Forwarded-For in all requests` section. ##### Optimize workflow queries Run the following SQL queries to optimize workflow performance: ``` CREATE INDEX idx_workflow_co_id_ver ON ezeditorialworkflow_workflows(content_id, version_no); CREATE INDEX idx_workflow_name ON ezeditorialworkflow_workflows(workflow_name); ``` ## 5. Finish the update ### A. Platform.sh changes If you're hosting your site on Ibexa Cloud be aware of the fact that Varnish is enabled by default as of v1.13.5, v2.4.3 and v2.5.0. If you're using Fastly, read about [how to disable Varnish](https://docs.platform.sh/frameworks/ibexa/fastly.html#remove-varnish-configuration). ### B. Dump assets Dump web assets if you're using the `prod` environment. In `dev` this happens automatically: ``` yarn install yarn encore prod ``` If you encounter problems, additionally clear the cache and install assets: ``` php bin/console cache:clear -e prod php bin/console assets:install --symlink -e prod yarn install yarn encore prod ``` ### C. Commit, test and merge When you resolve all conflicts and update `composer.lock`, commit the merge. You may or may not keep `composer.lock`, depending on your version management workflow. If you don't want to keep it, run `git reset HEAD composer.lock` to remove it from the changes. Run `git commit`, and adapt the message if necessary. Go back to `master`, and merge the `update-2.5` branch: ``` git checkout master git merge update-2.5 ``` > **Note: Insecure password hashes** > > To ensure that no users have unsupported, insecure password hashes, run the following command: > > ``` > # In v1 and v2: > php bin/console ezplatform:user:validate-password-hashes > # In v3: > php bin/console ibexa:user:validate-password-hashes > ``` > > This command checks if all user hashes are up-to-date and informs you if any of them need to be updated. ### D. Complete the update Complete the update by running the following commands: ``` # In v2.5: php bin/console ezplatform:graphql:generate-schema # In v3: php bin/console ibexa:graphql:generate-schema composer run post-install-cmd ``` ## Notify support Inform the support team that you have updated your installation. They update your Service portal to match the new version. This ensures that you receive notifications about new maintenance releases and security advisories for the correct version. You can contact the support team at [support@ibexa.co](mailto:support@ibexa.co) or through your [Service portal](https://support.ibexa.co). `defaultLayout` setting not available If you migrated you installation from eZ Publish Platform, in Page Builder you can encounter an issue where the **Default layout** dropdown is disabled with a "Layout '' for setting 'defaultLayout' is not available" error message. If this happens, add the following temporary configuration to `app/config/ezplatform.yml`: ``` ezpublish: system: global: ezpage: layouts: GlobalZoneLayout: name: Global zone layout template: globalzonelayout.tpl 2ZonesLayout1: name: 2 zones (layout 1) template: 2zoneslayout1.tpl 2ZonesLayout2: name: 2 zones (layout 2) template: 2zoneslayout2.tpl 2ZonesLayout3: name: 2 zones (layout 3) template: 2zoneslayout3.tpl 3ZonesLayout1: name: 3 zones (layout 1) template: 3zoneslayout1.tpl 3ZonesLayout2: name: 3 zones (layout 2) template: 3zoneslayout2.tpl CallForActionLayout: name: Call For Action zone layout template: callforactionlayout.tpl ``` Clear the cache and refresh the page. The dropdown should now be active. Select any option in the dropdown and save the content type. You should now be able to remove the field definition from the content type. Afterwards, you can remove the configuration above from `ezplatform.yml`. ## Update to v3.3 It's strongly recommended to also [update to the latest LTS, v3.3](https://doc.ibexa.co/en/latest/update_and_migration/from_2.5/update_from_2.5/index.md). # From 2.5 This update procedure applies if you're using v2.5. Go through the following steps to update to the v3.3 LTS (v3.3.43). Afterwards, it's strongly recommended to also [update to the latest v4.6 LTS](https://doc.ibexa.co/en/latest/update_and_migration/from_3.3/to_4.0/index.md). 1. [Check out a version](https://doc.ibexa.co/en/latest/update_and_migration/from_2.5/to_3.2/index.md) 2. [Resolve conflicts](https://doc.ibexa.co/en/latest/update_and_migration/from_2.5/to_3.2/#2-resolve-conflicts) 3. [Update the app](https://doc.ibexa.co/en/latest/update_and_migration/from_2.5/to_3.2/#3-update-the-app) 4. [Update code to v3](https://doc.ibexa.co/en/latest/update_and_migration/from_2.5/adapt_code_to_v3/index.md) 5. [Update to v3.3](https://doc.ibexa.co/en/latest/update_and_migration/from_2.5/to_3.3/index.md) 6. [Update the database](https://doc.ibexa.co/en/latest/update_and_migration/from_2.5/to_3.3/#6-update-the-database) 7. [Update to the latest patch version](https://doc.ibexa.co/en/latest/update_and_migration/from_3.3/update_from_3.3/index.md) 8. [Finish the update](https://doc.ibexa.co/en/latest/update_and_migration/from_3.3/update_from_3.3/#finish-the-update) # Update the app to v3.2 > **Caution: Caution** > > Before you start updating to v3.3, make sure that you're currently using the latest version of v2.5 (v2.5.32). If not, refer to the [update guide for v2.5](https://doc.ibexa.co/en/latest/update_and_migration/from_1.x_2.x/update_db_to_2.5/#d-update-to-v25). To move from v2.5 to v3.3, first, you need to bring the app to version v3.2. ## 1. Check out a version ### A. Create branch Create a new branch for handling update changes from the branch you're updating on: ``` git checkout -b update-3.2 ``` This creates a new project branch (`update-3.2`) for the update based on your current project branch. ### B. Add `upstream` remote If it's not added as a remote yet, add an `upstream` remote: **ezplatform** ``` git remote add upstream http://github.com/ezsystems/ezplatform.git ``` **ezplatform-ee** ``` git remote add upstream http://github.com/ezsystems/ezplatform-ee.git ``` **ezcommerce** ``` git remote add upstream http://github.com/ezsystems/ezcommerce.git ``` ### C. Prepare for pulling changes Adding `sort-packages` option when updating from \<=v1.13.4, v2.2.3, v2.3.2 Composer sorts packages listed in `composer.json`. If your packages aren't sorted yet, you should prepare for this update to make it clearer which changes you introduce. Assuming you have installed packages on your installation (`composer install`), do the following steps: 1. Add [sort-packages](https://getcomposer.org/doc/06-config.md#sort-packages) to the `config` section in `composer.json`. ``` "config": { "bin-dir": "bin", "sort-packages": true, "preferred-install": { "ezsystems/*": "dist" } }, ``` 2. Use `composer require` to get Composer to sort your packages. The following example updates a few requirements with what you can expect in the upcoming change: ``` composer require --no-scripts --no-update doctrine/doctrine-bundle:^1.9.1 composer require --dev --no-scripts --no-update behat/behat:^3.5.0 # The upcoming change also moves security-advisories to dev as advised by the package itself composer require --dev --no-scripts --no-update roave/security-advisories:dev-master ``` 3. Check that you can install/update packages. ``` composer update ``` If Composer says there were no updates, or if it updates packages without stopping with conflicts, your preparation was successful. 4. Save your work. ``` git commit -am "Sort my existing composer packages in anticipation of update with sorted merge" ``` ### D. Pull the tag into your branch Pull the latest v3.2 tag into the `update-3.2` branch with the following command: ``` git pull upstream v3.2.8 ``` At this stage you may get conflicts, which are a normal part of the update procedure. ## 2. Resolve conflicts ### A. Resolve conflicts If you get a lot of conflicts and you installed from the [support.ez.no / support.ibexa.co](https://support.ibexa.co) tarball or from [ezplatform.com](https://ezplatform.com), you may have incomplete history. To load the full history, run `git fetch upstream --unshallow` from the `update-3.2` branch, and run the merge again. Ignore the conflicts in `composer.lock`, because this file is regenerated when you execute `composer update` later. It's easiest to check out the version of `composer.lock` from the tag and add it to the changes: ``` git checkout --theirs composer.lock && git add composer.lock ``` If you don't keep a copy of `composer.lock` in the branch, you may also remove it by running: ``` git rm composer.lock ``` ### B. Resolve conflicts in `composer.json` You need to fix conflicts in `composer.json` manually. If you're not familiar with the diff output, you may check out the tag's version from the `update-3.2` branch and inspect the changes. ``` git checkout --theirs composer.json && git diff HEAD composer.json ``` This command shows the differences between the target `composer.json` and your own in the diff output. Updating `composer.json` changes the requirements for all of the `ezsystems` / `ibexa` packages. Keep those changes. The other changes remove what you added for your own project. Use `git checkout -p` to selectively cancel those changes (and retain your additions): ``` git checkout -p composer.json ``` Answer `no` (don't discard) to the requirement changes of `ezsystems` / `ibexa` dependencies. Answer `yes` (discard) to removals of your changes. After you're done, inspect the file (you can use an editor or run `git diff composer.json`). You may also test the file with `composer validate`, and test the dependencies by running `composer update --dry-run` (it outputs what it would do to the dependencies, without applying the changes). When finished, run `git add composer.json` and commit. ### C. Fix other conflicts Depending on the local changes you have done, you may get other conflicts, for example, on configuration files or kernel. For each change, edit the file, identify the conflicting changes, and resolve the conflict. Run `git add ` to add the changes. ## 3. Update the app At this point, you should have a `composer.json` file with the correct requirements and you can update dependencies. If you want to first test how the update proceeds without actually updating any packages, you can try the command with the `--dry-run` switch: ``` composer update --dry-run ``` Then, run `composer update` to update the dependencies. ``` composer update ``` ## Next steps Now, proceed to the next step, [updating the code to v3.0](https://doc.ibexa.co/en/latest/update_and_migration/from_2.5/adapt_code_to_v3/index.md). # Update code to v3 Before you start this procedure, make sure you have completed the previous step, [Updating to v3.2](https://doc.ibexa.co/en/latest/update_and_migration/from_2.5/to_3.2/index.md). ## 4. Update the code To adapt you installation to v3, you need to make a number of modifications to your code. ### New project structure > **Tip: Tip** > > If you run into issues, for details on all changes related to the switch to Symfony 5, see [Symfony upgrade guide for 4.0](https://github.com/symfony/symfony/blob/4.4/UPGRADE-4.0.md) and [for 5.0](https://github.com/symfony/symfony/blob/5.0/UPGRADE-5.0.md) The latest Symfony versions changed the organization of your project into folders and bundles. When updating to eZ Platform v3 you need to move your files and modify file paths and namespace references. *[Image: Project structure changes in v3]* #### Configuration Configuration files have been moved from `app/Resources/config` to `config`. Package-specific configuration is placed in `config/packages` (for example, `config/packages/ezplatform_admin_ui.yaml`). This folder also contains `config/packages/ezplatform.yaml`, which contains all settings coming in from Kernel. #### PHP code and bundle organization Since Symfony 4 `src/` code is no longer organized in bundles, `AppBundle` has been removed from the default eZ Platform install. To adapt, you need to move all your PHP code, such as controllers or event listeners, to the `src` folder and use the `App` namespace for your custom code instead. > **Tip: How to make AppBundle continue to work, for now** > > Refactoring bundles for `src/` folder can involve extensive changes, if you want to make your `src/AppBundle` continue to work, follow [an Autoloading src/AppBundle guide on Symfony Casts](https://symfonycasts.com/screencast/symfony4-upgrade/flex-composer.json#autoloading-src-amp-src-appbundle). > > You can also follow [Using a "path" Repository guide](https://symfonycasts.com/screencast/symfony-bundle/extracting-bundle#using-a-path-repository), to create a [composer path repository](https://getcomposer.org/doc/05-repositories.md#path). If you have several bundles you can move them into a `packages/` directory and load them all with: > > ``` > "repositories": [ > { "type": "path", "url": "packages/*" }, > ], > ``` > > Once you're ready to refactor the code to `App` namespace, follow [Bye Bye AppBundle](https://symfonycasts.com/screencast/symfony4-upgrade/bye-appbundle) article. #### View templates Templates are no longer stored in `app/Resources/views`. You need to move all your templates to the `templates` folder in your project's root. #### Translations Translation files have been moved out of `app/Resources/translations` into `translations` in your project's root. #### `web` and assets Content of the `web` folder is now placed in `public`. Content of `app/Resources/assets` has been moved to `assets`. > **Note: Note** > > You also need to update paths that refer to the old location, for example in [`webpack.config.js`](https://doc.ibexa.co/en/latest/administration/project_organization/project_organization/#importing-configuration-from-a-bundle). > **Note: Full list of deprecations** > > If you encounter any issue during the upgrade, see [eZ Platform v3.0 deprecations](https://doc.ibexa.co/en/latest/release_notes/ez_platform_v3.0_deprecations/#template-organization) for details of all required changes to your code. ### Third-party dependencies Because eZ Platform v3 is based on Symfony 5, you need to make sure all additional third-party dependencies that your project uses have been adapted to Symfony 5. ### Automatic code refactoring (optional) To simplify the process of adapting your code to Symfony 5, you can use [Rector, a reconstructor tool](https://github.com/rectorphp/rector) that automatically refactors your Symfony and PHPUnit code. To properly refactor your code, you might need to run the Rector `process` command for each Symfony version from 4.0 to 5.0 in turn: `vendor/bin/rector process src --set symfony40` You can find all the available sets in [the Rector repository](https://github.com/rectorphp/rector/tree/v0.7.65/config/set). Keep in mind that after automatic refactoring finishes there might be some code chunks that you need to fix manually. ### Update code for specific parts of the system Now, go through the following steps and ensure all your code is up to date with v3: - [1. Update templates](https://doc.ibexa.co/en/latest/update_and_migration/from_2.5/update_code/1_update_templates/index.md) - [2. Update configuration](https://doc.ibexa.co/en/latest/update_and_migration/from_2.5/update_code/2_update_configuration/index.md) - [3. Update field types](https://doc.ibexa.co/en/latest/update_and_migration/from_2.5/update_code/3_update_field_types/index.md) - [4. Update Signal Slots](https://doc.ibexa.co/en/latest/update_and_migration/from_2.5/update_code/4_update_signal_slots/index.md) - [5. Update Online Editor](https://doc.ibexa.co/en/latest/update_and_migration/from_2.5/update_code/5_update_online_editor/index.md) - [6. Update workflow](https://doc.ibexa.co/en/latest/update_and_migration/from_2.5/update_code/6_update_workflow/index.md) - [7. Update extended code](https://doc.ibexa.co/en/latest/update_and_migration/from_2.5/update_code/7_update_extensions/index.md) - [8. Update REST](https://doc.ibexa.co/en/latest/update_and_migration/from_2.5/update_code/8_update_rest/index.md) - [9. Other code updates](https://doc.ibexa.co/en/latest/update_and_migration/from_2.5/update_code/9_update_other/index.md) # 4.1. Update templates ## Back-Office templates The naming and location of templates in the back office have been changed. If you extend or modify these templates, you need to adapt your code. For the full list of template changes, see [the list of removals and deprecations](https://doc.ibexa.co/en/latest/release_notes/ez_platform_v3.0_deprecations/#template-organization). ## Twig functions and filters A number of [Twig functions, filters and helpers have been renamed](https://doc.ibexa.co/en/latest/release_notes/ez_platform_v3.0_deprecations/#functions-renamed). If your templates use them, you need to update them. ## Templating component [The templating component integration is now deprecated.](https://symfony.com/blog/new-in-symfony-4-3-deprecated-the-templating-component-integration) As a result, the way to indicate a template path has changed. For example: - **Use:** `"@@EzPlatformUser/user_settings/list.html.twig"` **instead of:** `"EzPlatformUserBundle:user_settings:list.html.twig"` - **Use:** `{% extends "@EzPublishCore/content_fields.html.twig" %}` **instead of:** `{% extends "EzPublishCoreBundle::content_fields.html.twig" %}` ## Form templates Content type editing has been [moved from `repository-forms` to `ezplatform-admin-ui`](https://doc.ibexa.co/en/latest/release_notes/ez_platform_v3.0_deprecations/#content-type-forms). Forms for content creation have been [moved from `repository-forms` to `ezplatform-content-forms`](https://doc.ibexa.co/en/latest/release_notes/ez_platform_v3.0_deprecations/#repository-forms). If your templates extend any of those built-in templates, you need to update their paths. ## Deprecated controller actions If your templates still use the deprecated `viewLocation` and `embedLocation` actions of `ViewController`, you need to rewrite them to use `viewAction` and `embedAction` respectively. ## Referencing controller actions To reference a controller, you now need to use `serviceOrFqcn::method` syntax instead of `bundle:controller:action`: **Use:** `controller: My\ExampleBundle\Controller\DefaultController::articleViewAction` **Instead of:** `controller: AcmeExampleBundle:Default:articleView` # 4.2. Update configuration ## `ezpublish` configuration key The main YAML configuration key is now [`ezplatform` instead of `ezpublish`](https://doc.ibexa.co/en/latest/release_notes/ez_platform_v3.0_deprecations/#configuration-through-ezplatform). You need to change your configuration files to make use of the new key. For example: **Use:** ``` ezplatform: system: default: # ... ``` **instead of:** ``` ezpublish: system: default: # ... ``` ## Resolving settings If you used dynamic settings (through `$setting$`), or got settings from the [ConfigResolver](https://doc.ibexa.co/en/latest/administration/configuration/dynamic_configuration/#configresolver) in a class constructor, you now need to rewrite your code to inject the ConfigResolver and get the relevant setting: **Use:** ``` use eZ\Publish\Core\MVC\ConfigResolverInterface; class MyService { /** @var \eZ\Publish\Core\MVC\ConfigResolverInterface */ private $configResolver; public function __construct(ConfigResolverInterface $configResolver) { $this->configResolver = $configResolver; } public function myMethodWhichUsesSetting(): void { $setting = $this->configResolver->getParameter('setting'); } } ``` **instead of:** ``` use eZ\Publish\Core\MVC\ConfigResolverInterface; class MyService { public function __construct(ConfigResolverInterface $configResolver) { $this->setting = $configResolver->getParameter('setting'); } } ``` # 4.3. Update field types You need to adapt your custom field types to the new field type architecture. ## `eZ\Publish\SPI\FieldType\FieldType` interface The `eZ\Publish\SPI\FieldType\FieldType` interface is now an abstract class. You need to replace `implements FieldType` in your field type code with `extends FieldType`. ## Deprecated `getName` method The deprecated method `getName` from the `eZ\Publish\SPI\FieldType\FieldType` interface has been changed. Now it accepts two additional parameters: `FieldDefinition $fieldDefinition` and `string $languageCode`. In your code you need to change the `getName` signature to `function getName(Value $value, FieldDefinition $fieldDefinition, string $languageCode): string;`. ## `eZ\Publish\SPI\FieldType\Nameable` interface The `eZ\Publish\SPI\FieldType\Nameable` interface has been removed. In your code you need to remove implementations of `Nameable` and replace them with `eZ\Publish\SPI\FieldType\FieldType::getName`. ## Deprecated tags You need to replace deprecated tags in service configuration: | Deprecated tag | Current tag | | -------------------------------------------------- | ------------------------------------------------------ | | ezpublish.fieldType.parameterProvider | ezplatform.field_type.parameter_provider | | ezpublish.fieldType.externalStorageHandler | ezplatform.field_type.external_storage_handler | | ezpublish.fieldType.externalStorageHandler.gateway | ezplatform.field_type.external_storage_handler.gateway | | ezpublish.fieldType | ezplatform.field_type | | ezpublish.fieldType.indexable | ezplatform.field_type.indexable | | ezpublish.storageEngine.legacy.converter | ezplatform.field_type.legacy_storage.converter | | ez.fieldFormMapper.definition | ezplatform.field_type.form_mapper.definition | | ez.fieldFormMapper.value | ezplatform.field_type.form_mapper.value | ## Moved classes You need to replace importing the following classes: | Previous location | Current location | | ------------------------------------------------------------------------- | --------------------------------------------------------------------------- | | EzSystems\\RepositoryForms\\Data\\Content\\FieldData | EzSystems\\EzPlatformContentForms\\Data\\Content\\FieldData | | EzSystems\\RepositoryForms\\Data\\FieldDefinitionData | EzSystems\\EzPlatformAdminUi\\Form\\Data\\FieldDefinitionData | | EzSystems\\RepositoryForms\\FieldType\\FieldDefinitionFormMapperInterface | EzSystems\\EzPlatformAdminUi\\FieldType\\FieldDefinitionFormMapperInterface | | EzSystems\\RepositoryForms\\FieldType\\FieldValueFormMapperInterface | EzSystems\\EzPlatformContentForms\\FieldType\\FieldValueFormMapperInterface | ## Extending field type templates If you extended templates for `ezobjectrelationlist_field`, `ezimageasset_field`, or `ezobjectrelation_field` fields using `{% extends "@EzPublishCore/content_fields.html.twig" %}`, you now need to extend `EzSystemsPlatformHttpCache` instead, if you wish to make use of cache: `{% extends "@EzSystemsPlatformHttpCache/content_fields.html.twig" %}`. # 4.4. Update Signal Slots If you used Signal Slots to listen for events in you custom code, you need to rewrite them using Symfony Events and Listeners instead. The application now triggers two Events per operation: one before and one after the relevant thing happens (see for example [Bookmark events](https://github.com/ezsystems/ezplatform-kernel/blob/v1.0.0/eZ/Publish/Core/Event/BookmarkService.php)). To use them, create [Event Listeners](https://symfony.com/doc/7.4/event_dispatcher.html) in your code, for example: **Use:** ``` public static function getSubscribedEvents(): array { return [ CreateBookmarkEvent::class => 'onCreateBookmark', ] } public function onCreateBookmark(CreateBookmarkEvent $event): void { /// your code } ``` **instead of:** ``` public function receive(Signal $signal) { if (!($signal instanceof CreateBookmarkSignal)) { return; } } // your code ``` # 4.5. Update Online Editor ## RichText Deprecated code related to the RichText field type has been removed from `ezpublish-kernel`. If your code still relies on the `eZ\Publish\Core\FieldType\RichText` namespace, you need to rewrite it to use `EzSystems\EzPlatformRichText\eZ\RichText` instead. ## Extra buttons Configuring custom Online Editor buttons with `ezrichtext.alloy_editor.extra_buttons` is deprecated. If you added custom buttons in this way, you need to rewrite your code to use `ezplatform.system..fieldtypes.ezrichtext.toolbars..buttons` instead. # 4.6. Update workflow [`flex-workflow` has been combined with `ezplatform-workflow`](https://doc.ibexa.co/en/latest/release_notes/ez_platform_v3.0_deprecations/#flex-workflow) in the form of a Quick Review functionality. If you used custom subscribers for events in workflow, you can now rewrite this code to use [custom actions](https://doc.ibexa.co/en/latest/content_management/workflow/add_custom_workflow_action/index.md). To migrate your content which had been using Flex Workflow to the new Quick Review workflow, run the following command: `php bin/console ezplatform:migrate:flex-workflow` # 4.7. Update extended code ## Universal Discovery Widget If you extended the Universal Discovery Widget (for example, added your own tabs or triggered opening the UDW for your own customizations), you need to rewrite this extension using the [new YAML configuration](https://doc.ibexa.co/en/3.3/extending/extending_udw/). ## Back office extensibility If you added custom tab groups in the back office, you now need to [make use of the `TabsComponent`](https://doc.ibexa.co/en/latest/administration/back_office/back_office_tabs/back_office_tabs/#tab-groups). # 4.8. Update REST If your code extends the REST API, you need to modify namespaces. The `eZ\Publish\Core\REST` and `eZ\Publish\Core\REST\Common\` namespaces have been replaced by `EzSystems\EzPlatformRest`. This is due to the fact that REST code has been moved from Kernel to a new `ezpublish-rest` package. ## Custom installers eZ Platform provides extension point to create named custom installer which can be used instead of the native one. To use it, execute the Symfony command: ``` php ./bin/console ezplatform:install ``` In eZ Platform v3.0, service definitions around that extension point have changed: 1. The deprecated Clean Installer has been dropped from `ezpublish-kernel` package. If your project uses custom installer and has relied on Clean Installer service definition (`ezplatform.installer.clean_installer`) you need to switch to Core Installer. **Use:** ``` services: Acme\App\Installer\MyCustomInstaller: parent: EzSystems\PlatformInstallerBundle\Installer\CoreInstaller ``` **instead of**: ``` services: Acme\App\Installer\MyCustomInstaller: parent: ezplatform.installer.clean_installer ``` `CoreInstaller` relies on [`DoctrineSchemaBundle`](https://github.com/ezsystems/doctrine-dbal-schema). Custom schema can be installed defining Symfony Event Subscriber subscribing to `EzSystems\DoctrineSchema\API\Event\SchemaBuilderEvents::BUILD_SCHEMA` event. 2. The deprecated Symfony Service definition `ezplatform.installer.db_based_installer` has been removed in favor of its FQCN-named definition. **Use:** ``` services: Acme\App\Installer\MyCustomInstaller: parent: EzSystems\PlatformInstallerBundle\Installer\DbBasedInstaller ``` **instead of:** ``` services: Acme\App\Installer\MyCustomInstaller: parent: ezplatform.installer.db_based_installer ``` # 4.9 Other code updates ## HTTP cache HTTP cache bundle now uses FOS Cache Bundle v2. If your code makes use of HTTP cache bundle, see [the list of changes and deprecations](https://doc.ibexa.co/en/latest/release_notes/ez_platform_v3.0_deprecations/#ezplatform-http-cache). ## User checker Add the user checker to firewall by adding the following line to `config/packages/security.yaml`: ``` security: firewalls: ezpublish_front: # ... user_checker: eZ\Publish\Core\MVC\Symfony\Security\UserChecker # ... ``` ## Commands The `ContainerAwareCommand` class isn't available in Symfony 5. Therefore, if your custom commands use `Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand` as a base class, you must rewrite them to use `Symfony\Component\Console\Command\Command` instead. ## Permissions Some [permission choice loaders](https://doc.ibexa.co/en/latest/release_notes/ez_platform_v3.0_deprecations/#code-cleanup-in-ez-platform-kernel) have been removed. If your code uses them, you must rewrite it to use the permission resolver. ## Service container parameters A number of Symfony [service container](https://doc.ibexa.co/en/latest/api/php_api/php_api/#service-container) parameters [have been dropped](https://github.com/ezsystems/ezplatform-kernel/blob/v1.0.0/doc/bc/1.0/dropped-container-parameters.md). Check if your code uses such invalid parameters: search for them by using the `ezpublish\..*\.class` regular expression pattern. When found, replace all the occurrences with fully-qualified class names. ## QueryTypes If your code relies on automatically registering QueryTypes through the naming convention `\QueryType\*QueryType`, you need to register your QueryTypes as services and tag them with `ezpublish.query`, or enable their automatic configuration (`autoconfigure: true`). ## Symfony namespaces A number of Symfony namespaces have changed, and you must update your code if it uses them. For example, the following namespaces are now different: | Use | Instead of | | ---------------------------------------------------- | ---------------------------------------------------- | | Symfony\\Contracts\\Translation\\TranslatorInterface | Symfony\\Component\\Translation\\TranslatorInterface | | Symfony\\Contracts\\EventDispatcher\\Event | Symfony\\Component\\EventDispatcher\\Event | For more information, search for removed classes in Symfony [version 4.0](https://github.com/symfony/symfony/blob/4.4/UPGRADE-4.0.md) and [version 5.0](https://github.com/symfony/symfony/blob/5.0/UPGRADE-5.0.md) documentation. ## Apache/Nginx configuration Make sure that your Apache/Nginx configuration is up to date with Symfony 5. Refer to [the provided `vhost.template`](https://github.com/ezsystems/ezplatform/blob/master/doc/apache2/vhost.template) for an example. ## Deprecations Due to a number of compatibility breaks and deprecations introduced in eZ Platform v3.0, the changes that result from the above considerations might not be sufficient. Make sure that you review your code and account for all changes listed in [Deprecations and backwards compatibility breaks](https://doc.ibexa.co/en/latest/release_notes/ez_platform_v3.0_deprecations/index.md). ## Next steps Now, proceed to the next step, [updating to v3.3](https://doc.ibexa.co/en/latest/update_and_migration/from_2.5/to_3.3/index.md). # Update the app to v3.3 Before you start this procedure, make sure you have completed the previous step, [Updating code to v3](https://doc.ibexa.co/en/latest/update_and_migration/from_2.5/adapt_code_to_v3/index.md). ## 5. Update to v3.3 Ibexa DXP v3.3 uses [Symfony Flex](https://symfony.com/doc/7.4/quick_tour/flex_recipes.html). When updating from v3.2 to v3.3, you need to follow a special update procedure. > **Note: Note** > > Ibexa DXP v3.3 requires Composer 2.0.13 or higher. First, create an update branch `update-3.3` in git and commit your work. If you haven't done it before, add the relevant meta-repository as an `upstream` remote: **ezplatform** ``` git remote add upstream http://github.com/ezsystems/ezplatform.git ``` **ezplatform-ee** ``` git remote add upstream http://github.com/ezsystems/ezplatform-ee.git ``` **ezcommerce** ``` git remote add upstream http://github.com/ezsystems/ezcommerce.git ``` > **Tip: Tip** > > It's good practice to make git commits after every step of the update procedure. ### A. Merge project skeleton Merge the current skeleton into your project: **Ibexa Contentt** ``` git remote add content-skeleton https://github.com/ibexa/content-skeleton.git git fetch content-skeleton --tags git merge v3.3.43 --allow-unrelated-histories ``` **Ibexa Experience** ``` git remote add experience-skeleton https://github.com/ibexa/experience-skeleton.git git fetch experience-skeleton --tags git merge v3.3.43 --allow-unrelated-histories ``` **Ibexa Commerce** ``` git remote add commerce-skeleton https://github.com/ibexa/commerce-skeleton.git git fetch commerce-skeleton --tags git merge v3.3.43 --allow-unrelated-histories ``` This introduces changes from the relevant website skeleton and results in conflicts. Resolve the conflicts in the following way: - Make sure all automatically added `ezsystems/*` packages are removed. If you explicitly added any packages that aren't part of the standard installation, retain them. - Review the rest of the packages. If your project requires a package, keep it. - If a package is only used as a dependency of an `ezsystems` package, remove it. You can check how the package is used with `composer why `. - Keep the dependencies listed in the website skeleton. > **Tip: Tip** > > You can also approach resolving conflicts differently: run `git checkout --theirs composer.json` to get a clean `composer.json` from the skeleton and then manually add any necessary changes from your project. > **Caution: Caution** > > It's impossible to update an Enterprise edition (`ezsystems/ezplatform-ee`) to an Ibexa Content edition. > > Also, make sure that `composer.json` has the following `repositories` entry: > > ``` > "ibexa": { > "type": "composer", > "url": "https://updates.ibexa.co" > } > ``` ### B. Update the app Update Symfony Flex, then update the dependencies: ``` composer update symfony/flex --no-plugins --no-scripts composer update ``` > **Caution: Caution** > > Composer repository changes between 3.2 and 3.3 from `updates.ez.no` to `updates.ibexa.co`, therefore your credentials might be outdated. > > `username` and `password` don't change. The repository they're used on changes. > > See [Composer authentication documentation](https://getcomposer.org/doc/articles/authentication-for-private-packages.md) to find the precedure that suits the way you're passing credentials. > > In production, replace the old repository with the new one. But as a developer, you may need to go back to an earlier version, and should keep the old repository as well. For example, your `auth.json` may look like this: > > ``` > { > "http-basic": { > "updates.ibexa.co": { > "username": "abcdefghijklmnopqrstuvwxyz012345", > "password": "6789abcdefghijklmnopqrstuvwxyz01" > }, > "updates.ez.no": { > "username": "abcdefghijklmnopqrstuvwxyz012345", > "password": "6789abcdefghijklmnopqrstuvwxyz01" > } > } > } > ``` ### C. Configure the web server Add the following rewrite rule to your web server configuration: **Apache** ``` RewriteRule ^/build/ - [L] ``` **nginx** ``` rewrite "^/build/(.*)" "/build/$1" break; ``` ## 6. Update the database Apply the following database update script: ``` mysql -u -p < vendor/ibexa/installer/upgrade/db/mysql/ezplatform-2.5-to-ibexa-3.3.0.sql ``` If you're updating from an installation based on the `ezsystems/ezplatform-ee` metarepository, run the following command to upgrade your database: ``` php bin/console ibexa:upgrade ``` > **Caution: Caution** > > You can only run this command once. Check the location ID of the "Components" content item and set it as a value of the `content_tree_module.contextual_tree_root_location_ids` key in `config/ezplatform.yaml`: ``` - 60 # Components ``` If you're upgrading between Ibexa Commerce versions, add the `content/read` policy with the Owner limitation set to `self` to the "Ecommerce registered users" role. ## 7. Update to the latest patch version Now, proceed to the last step, [updating to the latest v3.3 patch version](https://doc.ibexa.co/en/latest/update_and_migration/from_3.3/update_from_3.3/index.md). # Update from v3.3.x to v3.3.latest This update procedure applies if you're using a v3.3 installation without the latest maintenance release. To update from an 3.2 to 3.3, see [Updating the app to v3.3](https://doc.ibexa.co/en/latest/update_and_migration/from_2.5/to_3.3/index.md). From older version, explore [this section](https://doc.ibexa.co/en/latest/update_and_migration/update_ibexa_dxp/index.md). Go through the following steps to update to the latest maintenance release of v3.3 (v3.3.43). > **Note: Note** > > You can only update to the latest patch release of 3.3.x. ## Update the application > **Note: Note** > > If you're using v3.3.15 or earlier v3.3 version, or encounter an error related to flex.ibexa.co, you need to [update your Flex server](#update-flex-server) first. Run: **Ibexa Content** ``` composer require ibexa/content:3.3.43 --with-all-dependencies --no-scripts ``` **Ibexa Experience** ``` composer require ibexa/experience:3.3.43 --with-all-dependencies --no-scripts ``` **Ibexa Commerce** ``` composer require ibexa/commerce:3.3.43 --with-all-dependencies --no-scripts ``` To avoid deprecations when updating from an older PHP version to PHP 8.2 or 8.3, run the following commands: ``` composer config extra.runtime.error_handler "\\Ibexa\\Contracts\\Core\\MVC\\Symfony\\ErrorHandler\\Php82HideDeprecationsErrorHandler" composer dump-autoload ``` ### Update Flex server The `flex.ibexa.co` Flex server has been disabled. If you're using v3.3.15 or earlier v3.3 version, you need to update your Flex server. In your `composer.json` check whether the `https://flex.ibexa.co` endpoint is still listed in `extra.symfony.endpoint`. If that's the case, you need to perform the following update procedure. First, update the `symfony/flex` bundle to handle the new endpoint: ``` composer update symfony/flex --no-plugins --no-scripts; ``` Then, replace the `https://flex.ibexa.co` endpoint with the new [`https://api.github.com/repos/ibexa/recipes/contents/index.json?ref=flex/main`](https://github.com/ibexa/website-skeleton/blob/v3.3.20/composer.json#L98) endpoint in `composer.json` under `extra.symfony.endpoint`. You can do it manually, or by running the following command: ``` composer config extra.symfony.endpoint "https://api.github.com/repos/ibexa/recipes/contents/index.json?ref=flex/main" ``` Next, continue with updating the app: **Ibexa Content** ``` composer recipes:install ibexa/content --force -v composer run post-install-cmd ``` **Ibexa Experience** ``` composer recipes:install ibexa/experience --force -v composer run post-install-cmd ``` **Ibexa Commerce** ``` composer recipes:install ibexa/commerce --force -v composer run post-install-cmd ``` Review the changes to make sure your custom configuration wasn't affected. Remove the `vendor` folder to prevent issues related to the [new Flex server](#update-flex-server). Then, perform a database upgrade and other steps relevant to the version you're updating to. > **Caution: Clear Redis cache** > > If you're using Redis as your persistence cache storage you should always clear it manually after an upgrade. You can do it by executing the following command: > > ``` > php bin/console cache:pool:clear cache.redis > ``` ### v3.3.2 #### Update entity managers Version v3.3.2 introduces new entity managers. To ensure that they work in multi-repository setups, you must update the Doctrine schema. You do this manually by following this procedure: 1. Update your project to v3.3.2 and run the `php bin/console cache:clear` command to generate the service container. 1. Run the following command to discover the names of the new entity managers. Take note of the names that you discover: `php bin/console debug:container --parameter=doctrine.entity_managers --format=json | grep ibexa_` 1. For every entity manager prefixed with `ibexa_`, run the following command: `php bin/console doctrine:schema:update --em= --dump-sql` 1. Review the queries and ensure that there are no harmful changes that could affect your data. 1. For every entity manager prefixed with `ibexa_`, run the following command to run queries on the database: `php bin/console doctrine:schema:update --em= --force` #### VCL configuration for Fastly If you use Fastly, deploy the most up-to-date VCL configuration. Locate the `vendor/ezsystems/ezplatform-http-cache-fastly/fastly/ez_main.vcl` file, make sure that it has been updated with the following changes, and upload it to your Fastly: - Add the following lines: ``` if (req.restarts == 0 && resp.status == 301 && req.http.x-fos-original-url) { set resp.http.location = regsub(resp.http.location, "/_fos_user_context_hash", req.http.x-fos-original-url); } ``` - Move the `#FASTLY recv` macro call to a new location, right after the `Preserve X-Forwarded-For in all requests` section. #### Optimize workflow queries Run the following SQL queries to optimize workflow performance: ``` CREATE INDEX idx_workflow_co_id_ver ON ezeditorialworkflow_workflows(content_id, version_no); CREATE INDEX idx_workflow_name ON ezeditorialworkflow_workflows(workflow_name); ``` #### Enable Commerce features Commerce features in Experience and Content editions are disabled by default. If you use these features, after the update enable Commerce features by going to `config/packages/ecommerce.yaml` and setting the following: ``` ezplatform: system: default: commerce: enabled: true ``` Next, run the following command: ``` php bin/console ibexa:upgrade --force ``` #### Database update If you're using MySQL, run the following update script: ``` mysql -u -p < vendor/ibexa/installer/upgrade/db/mysql/ibexa-3.3.1-to-3.3.2.sql ``` ### v3.3.4 #### Migration Bundle Remove `Kaliop\eZMigrationBundle\eZMigrationBundle::class => ['all' => true],` from `config/bundles.php` before running `composer require`. Then, in `composer.json`, set minimum stability to `stable`: ``` "minimum-stability": "stable", ``` ### v3.3.6 #### Symfony 5.3 To update to Symfony 5.3, update the following package versions in your `composer.json`, including the Symfony version (line 9): ``` "symfony/flex": "^1.3.1" "sensio/framework-extra-bundle": "^6.1", "symfony/runtime": "*", "doctrine/doctrine-bundle": "^2.4" "symfony/maker-bundle": "^1.0", "symfony": { "allow-contrib": true, "require": "5.3.*", "endpoint": "https://flex.ibexa.co" }, ``` See for details of the package version change. ### v3.3.7 #### Commerce configuration If you're using Commerce, run the following migration action to update the way Commerce configuration is stored: ``` mkdir --parent src/Migrations/Ibexa/migrations cp vendor/ibexa/installer/src/bundle/Resources/install/migrations/content/Components/move_configuration_to_settings.yaml src/Migrations/Ibexa/migrations/ php bin/console ibexa:migrations:migrate --file=move_configuration_to_settings.yaml ``` #### Database update Run the following scripts: **MySQL** ``` mysql -u -p < vendor/ibexa/installer/upgrade/db/mysql/ibexa-3.3.6-to-3.3.7.sql ``` **PostgreSQL** ``` psql < vendor/ibexa/installer/upgrade/db/postgresql/ibexa-3.3.6-to-3.3.7.sql ``` ### Ibexa Open Source If you have no access to Ibexa DXP's `ibexa/installer` package, apply the following database upgrade script: **MySQL** ``` DROP TABLE IF EXISTS `ibexa_setting`; CREATE TABLE `ibexa_setting` ( `id` int(11) NOT NULL AUTO_INCREMENT, `group` varchar(128) COLLATE utf8mb4_unicode_520_ci NOT NULL, `identifier` varchar(128) COLLATE utf8mb4_unicode_520_ci NOT NULL, `value` text COLLATE utf8mb4_unicode_520_ci NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `ibexa_setting_group_identifier` (`group`, `identifier`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; ``` **PostgreSQL** ``` DROP TABLE IF EXISTS ibexa_setting; CREATE TABLE ibexa_setting ( id SERIAL NOT NULL, "group" varchar(128) NOT NULL, identifier varchar(128) NOT NULL, value json NOT NULL, PRIMARY KEY (id), CONSTRAINT ibexa_setting_group_identifier UNIQUE ("group", identifier) ); ``` ### v3.3.9 #### Database update Run the following scripts: **MySQL** ``` mysql -u -p < vendor/ibexa/installer/upgrade/db/mysql/ibexa-3.3.8-to-3.3.9.sql ``` **PostgreSQL** ``` psql < vendor/ibexa/installer/upgrade/db/postgresql/ibexa-3.3.8-to-3.3.9.sql ``` ### v3.3.13 > **Note: Symfony 5.4** > > Prior to v3.3.13, Symfony 5.3 was used by default. > > If you're still using Symfony 5.3, you need to update your installation to Symfony 5.4. To do this, update your `composer.json` to refer to `5.4.*` instead or `5.3.*`. > > Refer to the relevant website skeleton: [content](https://github.com/ibexa/content-skeleton/blob/v3.3.13/composer.json), [experience](https://github.com/ibexa/experience-skeleton/blob/v3.3.13/composer.json), [commerce](https://github.com/ibexa/commerce-skeleton/blob/v3.3.13/composer.json). > > The following `sed` commands should update the relevant lines. Use them with caution and properly check the result: > > ``` > sed -i -E 's/"symfony\/(.+)": "5.3.*"/"symfony\/\1": "5.4.*"/' composer.json; > sed -i -E 's/"require": "5.3.*"/"require": "5.4.*"/' composer.json; > ``` > > After this `composer.json` update, run `composer update "symfony/*"`. > > You may need to adapt configuration to fit the new minor version of Symfony. For example, you might have to remove `timeout` related config from `nelmio_solarium` bundle config: > > ``` > sed -i -E '/ *timeout: [0-9]+/d' ./config/packages/nelmio_solarium.yaml ./config/packages/ezcommerce/ezcommerce_advanced.yaml > composer update "symfony/*" > ``` #### Ibexa Cloud Update Platform.sh configuration and scripts. Generate new configuration with the following command: ``` composer ibexa:setup --platformsh ``` Review the changes applied to `.platform.app.yaml`, `.platform/` and `bin/platformsh_prestart_cacheclear.sh`, merge with your custom settings if needed, and commit them to Git. ### v3.3.14 #### VCL configuration Update your Varnish VCL file to align with [`docs/varnish/vcl/varnish5.vcl`](https://github.com/ezsystems/ezplatform-http-cache/blob/2.3/docs/varnish/vcl/varnish5.vcl). Make sure it contains the highlighted additions. ``` // Compressing the content // ... // Modify xkey header to add translation suffix if (beresp.http.xkey && beresp.http.x-lang) { set beresp.http.xkey = beresp.http.xkey + " " + regsuball(beresp.http.xkey, "(\S+)", "\1" + beresp.http.x-lang); } // ... if (client.ip ~ debuggers) { /// ... } else { // Remove tag headers when delivering to non debug client unset resp.http.xkey; unset resp.http.x-lang; // Sanity check to prevent ever exposing the hash to a non debug client. unset resp.http.x-user-context-hash; } ``` ### v3.3.15 Adapt your `composer.json` file according to [`manifest.json`](https://github.com/ibexa/recipes/blob/master/ibexa/commerce/3.3/manifest.json#L167-L168), by adding and moving the following lines: ``` "composer-scripts": { "cache:clear": "symfony-cmd", "assets:install %PUBLIC_DIR%": "symfony-cmd", - "bazinga:js-translation:dump %PUBLIC_DIR%/assets --merge-domains": "symfony-cmd", "yarn install": "script", + "ibexa:encore:compile --config-name app": "symfony-cmd", + "bazinga:js-translation:dump %PUBLIC_DIR%/assets --merge-domains": "symfony-cmd", "ibexa:encore:compile": "symfony-cmd" } ``` ### v3.3.16 See [Update Flex server](#update-flex-server). ### v3.3.24 #### VCL configuration for Fastly Ibexa DXP now supports Fastly shielding. If you're using Fastly and want to use shielding, you need to update your VCL files. > **Tip: Tip** > > Even if you don't plan to use Fastly shielding, it's recommended to update the VCL files for future compatibility. 1. Locate the `vendor/ezsystems/ezplatform-http-cache-fastly/fastly/ez_main.vcl` file and update your VCL file with the recent changes. 2. Do the same with `vendor/ezsystems/ezplatform-http-cache-fastly/fastly/ez_user_hash.vcl`. 3. Upload a new `snippet_re_enable_shielding.vcl` snippet file, based on `vendor/ezsystems/ezplatform-http-cache-fastly/fastly/snippet_re_enable_shielding.vcl`. ### v3.3.25 #### Database update On Experience or Commerce edition, run the following scripts: **MySQL** ``` mysql -u -p < vendor/ibexa/installer/upgrade/db/mysql/ibexa-3.3.24-to-3.3.25.sql ``` **PostgreSQL** ``` psql < vendor/ibexa/installer/upgrade/db/postgresql/ibexa-3.3.24-to-3.3.25.sql ``` ### v3.3.28 #### Ensure password safety Following [Security advisory: IBEXA-SA-2022-009](https://developers.ibexa.co/security-advisories/ibexa-sa-2022-009-critical-vulnerabilities-in-graphql-role-assignment-ct-editing-and-drafts-tooltips), unless you can verify based on your log files that the vulnerability hasn't been exploited, you should [revoke passwords](https://doc.ibexa.co/en/latest/users/passwords/#revoking-passwords) for all affected users. ### v3.3.34 #### Database update Run the following scripts: **MySQL** ``` mysql -u -p < vendor/ibexa/installer/upgrade/db/mysql/ibexa-3.3.33-to-3.3.34.sql ``` **PostgreSQL** ``` psql < vendor/ibexa/installer/upgrade/db/postgresql/ibexa-3.3.33-to-3.3.34.sql ``` ### v3.3.40 No additional steps needed. ### v3.3.41 #### Security This release contains security fixes. For more information, see [the published security advisory](https://developers.ibexa.co/security-advisories/ibexa-sa-2024-006-vulnerabilities-in-content-name-pattern-commerce-shop-and-varnish-vhost-templates). For each of the following fixes, evaluate the vulnerability to determine whether you might have been affected. If so, take appropriate action, for example by [revoking passwords](https://doc.ibexa.co/en/latest/users/passwords/#revoking-passwords) for all affected users. ##### BREACH vulnerability The [BREACH](https://www.breachattack.com/) attack is a security vulnerability against HTTPS when using HTTP compression. If you're using Varnish, update the VCL configuration to stop compressing both the Ibexa DXP's REST API and JSON responses from your backend. Fastly users are not affected. **Varnish on Ibexa Cloud** Update the Varnish configuration. Generate new configuration with the following command: ``` composer ibexa:setup --platformsh ``` Review the changes, merge with your custom settings if needed, and commit them to Git before deployment. **Varnish 6** Update your Varnish VCL file to align it with the [`vendor/ezsystems/ezplatform-http-cache/docs/varnish/vcl/varnish5.vcl`](https://github.com/ezsystems/ezplatform-http-cache/blob/2.3/docs/varnish/vcl/varnish5.vcl) file. **Varnish 7** Update your Varnish VCL file to align it with the [`vendor/ezsystems/ezplatform-http-cache/docs/varnish/vcl/varnish7.vcl`](https://github.com/ezsystems/ezplatform-http-cache/blob/2.3/docs/varnish/vcl/varnish7.vcl) file. ``` If you're not using a reverse proxy like Varnish or Fastly, adjust the compressed `Content-Type` in the web server configuration. For more information, see the [updated Apache and nginx template configuration](https://github.com/ibexa/post-install/pull/86/files). ##### Outdated version of jQuery in ezsystems/ezcommerce-shop package There are no additional update steps to execute. #### Other changes ##### Remove duplicated entries in `ezcontentobject_attribute` table This release comes with a command to clean up duplicated entries in the `ezcontentobject_attribute` table, which were created due to an issue related to previewing content in different languages. If you're affected, remove the duplicated entries by running the following command: ``` php bin/console ibexa:content:remove-duplicate-fields ``` > **Caution: Caution** > > Remember about [**proper database backup**](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/backup/index.md) before running the command in the production environment. You can customize the behavior of the command with the following options: - `--batch-size` or `-b` - number of attributes affected per iteration. Default value = 10000. - `--max-iterations` or `-i` - maximum iterations count. Default value = -1 (unlimited). - `--sleep` or `-s` - wait time between iterations, in milliseconds. Default value = 0. ##### Update web server configuration Adjust the web server configuration to prevent direct access to the `index.php` file when using URLs consisting of multiple path segments. See [the updated Apache and nginx template files](https://github.com/ibexa/post-install/pull/70/files) for more information. #### Removed `symfony/serializer-pack` dependency This release no longer directly requires the `symfony/serializer-pack` Composer dependency, which can remove some dependencies from your project during the update process. If you rely on them in your project, for example by using Symfony's `ObjectNormalizer` to create your own REST endpoints, run the following command before updating Ibexa packages: ``` composer require symfony/serializer-pack ``` Then, verify that Symfony Flex installed the versions you were using before. ### v3.3.42 #### Security This release fixes a critical vulnerability in the [RichText field type](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/richtextfield/index.md). By entering a maliciously crafted input into the RichText field type's XML, the attacker could perform an attack using [XML external entity (XXE) injection](https://portswigger.net/web-security/xxe). To exploit this vulnerability, an attacker would need to have edit permission to content with RichText fields. For more information, see the [published security advisory IBEXA-SA-2025-002](https://developers.ibexa.co/security-advisories/ibexa-sa-2025-002-xxe-vulnerability-in-richtext). Evaluate the vulnerability to determine whether you might have been affected. If so, take appropriate action. There are no additional update steps to execute. ### v3.3.43 #### Security This security advisory resolves XSS vulnerabilities in several parts of the back office of the DXP. Back office access and varying levels of editing and management permissions are required to exploit these vulnerabilities. For more information, see the [security advisory IBEXA-SA-2025-003](https://developers.ibexa.co/security-advisories/ibexa-sa-2025-003-xss-vulnerabilities-in-back-office). Evaluate the vulnerability to determine whether you might have been affected. If so, take appropriate action. There are no additional update steps to execute. ## Finish the update ### A. Platform.sh changes If you're hosting your site on Ibexa Cloud be aware of the fact that Varnish is enabled by default as of v1.13.5, v2.4.3 and v2.5.0. If you're using Fastly, read about [how to disable Varnish](https://docs.platform.sh/frameworks/ibexa/fastly.html#remove-varnish-configuration). ### B. Dump assets Dump web assets if you're using the `prod` environment. In `dev` this happens automatically: ``` yarn install yarn encore prod ``` If you encounter problems, additionally clear the cache and install assets: ``` php bin/console cache:clear -e prod php bin/console assets:install --symlink -e prod yarn install yarn encore prod ``` ### C. Commit, test and merge When you resolve all conflicts and update `composer.lock`, commit the merge. You may or may not keep `composer.lock`, depending on your version management workflow. If you don't want to keep it, run `git reset HEAD composer.lock` to remove it from the changes. Run `git commit`, and adapt the message if necessary. Go back to `master`, and merge the `update-{{ target_version }}` branch: ``` git checkout master git merge update-{{ target_version }} ```` > **Note: Insecure password hashes** > > To ensure that no users have unsupported, insecure password hashes, run the following command: > > ``` > # In v1 and v2: > php bin/console ezplatform:user:validate-password-hashes > # In v3: > php bin/console ibexa:user:validate-password-hashes > ``` > > This command checks if all user hashes are up-to-date and informs you if any of them need to be updated. ### D. Complete the update Complete the update by running the following commands: ```` # In v2.5: php bin/console ezplatform:graphql:generate-schema # In v3: php bin/console ibexa:graphql:generate-schema composer run post-install-cmd ```` ## Notify support Inform the support team that you have updated your installation. They update your Service portal to match the new version. This ensures that you receive notifications about new maintenance releases and security advisories for the correct version. You can contact the support team at [support@ibexa.co](mailto:support@ibexa.co) or through your [Service portal](https://support.ibexa.co).``` ```` # Update from v3.3.x to v4.0 This update procedure applies if you're using v3.3. Go through the following steps to update to v4.0. Besides updating the application and database, you need to account for changes related to code refactoring and numerous namespace changes. See [a list of all changed namespaces, configuration key, service names, and other changes](https://doc.ibexa.co/en/latest/release_notes/ibexa_dxp_v4.0_deprecations/index.md). An additional compatibility layer makes the process of updating your code easier. > **Caution: Temporary need of Composerconflict** > > To go through this update, [map the conflicting packages](https://getcomposer.org/doc/04-schema.md#conflict) in your `composer.json` file as following: > > ``` > "conflict": { > "jms/serializer": ">=3.30.0", > "gedmo/doctrine-extensions": ">=3.12.0" > }, > ``` > > These entries can be removed after fully upgrading to v4.6 LTS. > **Note: Symfony 5.4** > > If you're using Symfony 5.3, you need to update your installation to Symfony 5.4. To do this, update your composer.json to refer to `5.4.*` instead or `5.3.*`. > > Refer to the relevant website skeleton for an example: [content](https://github.com/ibexa/content-skeleton/blob/v4.0.1/composer.json), [experience](https://github.com/ibexa/experience-skeleton/blob/v4.0.1/composer.json), [commerce](https://github.com/ibexa/commerce-skeleton/blob/v4.0.1/composer.json). ## Update the app to v4.0 First, run: **Ibexa Content** ``` composer require ibexa/content:4.0.8 --with-all-dependencies --no-scripts ``` **Ibexa Experience** ``` composer require ibexa/experience:4.0.8 --with-all-dependencies --no-scripts ``` **Ibexa Commerce** ``` composer require ibexa/commerce:4.0.8 --with-all-dependencies --no-scripts ``` ### Update Flex server The `flex.ibexa.co` Flex server has been disabled. If you're using v4.0.2 or earlier v4.0 version, you need to update your Flex server. To do it, in your `composer.json` check whether the `https://flex.ibexa.co` endpoint is still listed in `extra.symfony.endpoint`. If so, replace it with the new [`https://api.github.com/repos/ibexa/recipes/contents/index.json?ref=flex/main`](https://github.com/ibexa/website-skeleton/blob/v4.0.7/composer.json#L98) endpoint. If your `composer.json` still uses the `https://flex.ibexa.co` endpoint in `extra.symfony.endpoint`, replace it with the new [`https://api.github.com/repos/ibexa/recipes/contents/index.json?ref=flex/main`](https://github.com/ibexa/website-skeleton/blob/v4.0.7/composer.json#L96) endpoint. You can do it manually, or by running the following command: ``` composer config extra.symfony.endpoint "https://api.github.com/repos/ibexa/recipes/contents/index.json?ref=flex/main" ``` Next, continue with updating the app: **Ibexa Content** ``` composer recipes:install ibexa/content --force -v ``` **Ibexa Experience** ``` composer recipes:install ibexa/experience --force -v ``` **Ibexa Commerce** ``` composer recipes:install ibexa/commerce --force -v ``` The `recipes:install` command installs new YAML configuration files, which have been [renamed in this release](https://doc.ibexa.co/en/latest/release_notes/ibexa_dxp_v4.0_deprecations/#configuration-file-names). Look through the old YAML files and move your custom configuration to the relevant new files. In `bundles.php`, remove all entries starting with `eZ`, `EzSystems`, `Ibexa\Platform`, `Silversolutions` and `Siso`. Leave only third-party entries and entries added by the `recipes:install` command, starting with `Ibexa\Bundle`. ## Add compatibility layer package You can use the provided compatibility layer to speed up adaptation of your custom code to the new namespaces. Add the compatibility layer package using Composer: ``` composer require ibexa/compatibility-layer composer recipes:install ibexa/compatibility-layer --force ``` Make sure that `Ibexa\Bundle\CompatibilityLayer\IbexaCompatibilityLayerBundle` is last in your bundle list in `config/bundles.php`. Next, clear the cache: ``` php bin/console cache:clear ``` ## Update the database Apply the following database update script: ### Ibexa DXP **MySQL** ``` mysql -u -p < vendor/ibexa/installer/upgrade/db/mysql/ibexa-3.3.latest-to-4.0.0.sql ``` **PostgreSQL** ``` psql < vendor/ibexa/installer/upgrade/db/postgresql/ibexa-3.3.latest-to-4.0.0.sql ``` ### Ibexa Open Source If you have no access to Ibexa DXP's `ibexa/installer` package, apply the following database upgrade script: **MySQL** ``` ALTER TABLE `ezcontentclassgroup` ADD COLUMN `is_system` BOOLEAN NOT NULL DEFAULT false; ``` **PostgreSQL** ``` ALTER TABLE "ezcontentclassgroup" ADD "is_system" boolean DEFAULT false NOT NULL; ``` ### Prepare new database tables For every database connection you have configured, perform the following steps: 1. Run `php bin/console doctrine:schema:update --dump-sql --em=ibexa_{connection}` 2. Check the queries and verify that they're safe and don't damage the data. 3. Run `php bin/console doctrine:schema:update --dump-sql --em=ibexa_{connection} --force` Next, run the following commands to import necessary data migration scripts: ``` php bin/console ibexa:migrations:import vendor/ibexa/taxonomy/src/bundle/Resources/install/migrations/content_types.yaml --name=000_taxonomy_content_types.yml php bin/console ibexa:migrations:import vendor/ibexa/taxonomy/src/bundle/Resources/install/migrations/sections.yaml --name=001_taxonomy_sections.yml php bin/console ibexa:migrations:import vendor/ibexa/taxonomy/src/bundle/Resources/install/migrations/content.yaml --name=002_taxonomy_content.yml php bin/console ibexa:migrations:import vendor/ibexa/taxonomy/src/bundle/Resources/install/migrations/permissions.yaml --name=003_taxonomy_permissions.yml php bin/console ibexa:migrations:import vendor/ibexa/product-catalog/src/bundle/Resources/migrations/product_catalog.yaml --name=001_product_catalog.yaml php bin/console ibexa:migrations:import vendor/ibexa/product-catalog/src/bundle/Resources/migrations/currencies.yaml --name=001_currencies.yaml ``` Run `php bin/console ibexa:migrations:migrate -v --dry-run` to ensure that all migrations are ready to be performed. If the dry run is successful, run: ``` php bin/console ibexa:migrations:migrate ``` ## Update your custom code ### GraphQL Some GraphQL names have changed. Adapt your queries according to the table below. | 3.3 name | 4.0 name | | ------------------------------------------------- | ------------------------------------------- | | `id` argument | `contentId` argument | | `_info` content item property | `_contentInfo` content item property | | `Content` (example: `FolderContent`) | `Item` (example: `FolderItem`) | Example of an updated query: | 3.3 | 4.0 | | --------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | | `{ content { folder(id: 1) { _info { id name } } } }` | `{ content { folder(contentId: 1) { _contentInfo{ id name } } } }` | Notice that the argument have been updated to `contentId` while the `id` property keeps its name. While revisiting GraphQL queries, you may consider the new feature `item` allowing to fetch a content item without knowing its content type. For more information, see [Get a content item](https://doc.ibexa.co/en/latest/api/graphql/graphql_queries/#get-a-content-item). ### Back office customization The v4 version of Ibexa DXP is using Bootstrap 5 in the back office. If you were using Bootstrap 4 for styling, you need to update and adjust all custom back office components [following the migration guide from Bootstrap 4](https://getbootstrap.com/docs/5.0/migration/). ### Online editor #### Custom plugins and buttons If you added your own Online Editor plugins or buttons, you need to rewrite them using [CKEditor 5's extensibility](https://ckeditor.com/docs/ckeditor5/latest/tutorials/crash-course/plugins.html#creating-custom-plugins). #### Custom tags If you created a custom tag, you need to adapt it to the new configuration, for example: ``` ibexa: system: admin_group: fieldtypes: ezrichtext: custom_tags: [ezfactbox] toolbar: custom_tags_group: buttons: ezfactbox: priority: 5 ``` ### Personalization In Personalization, the `included_content_types` configuration key has changed to `included_item_types`. Update your configuration, if it applies. ## Finish update Adapt your `composer.json` file according to [`manifest.json`](https://github.com/ibexa/recipes/blob/master/ibexa/commerce/4.0/manifest.json#L170-L171), by adding the following lines: ``` "yarn install": "script", "ibexa:encore:compile --config-name app": "symfony-cmd", "bazinga:js-translation:dump %PUBLIC_DIR%/assets --merge-domains": "symfony-cmd", "ibexa:encore:compile": "symfony-cmd" ``` Then, finish the update process: ``` composer run post-install-cmd ``` Finally, generate the new GraphQl schema: ``` php bin/console ibexa:graphql:generate-schema ``` ### Ibexa Cloud Update Platform.sh configuration and scripts. Generate new configuration with the following command: ``` composer ibexa:setup --platformsh ``` Review the changes applied to `.platform.app.yaml`, `.platform/` and `bin/platformsh_prestart_cacheclear.sh`, merge with your custom settings if needed, and commit them to Git. # Update from v4.0.x to v4.1 This update procedure applies if you're using v4.0.0. Go through the following steps to update to v4.1. > **Caution: Temporary need of Composerconflict** > > To go through this update, [map the conflicting packages](https://getcomposer.org/doc/04-schema.md#conflict) in your `composer.json` file as following: > > ``` > "conflict": { > "jms/serializer": ">=3.30.0", > "gedmo/doctrine-extensions": ">=3.12.0" > }, > ``` > > These entries can be removed after fully upgrading to v4.6 LTS. > **Note: Note** > > During the update process you can encounter the following error: > > `Failed to create closure from callable: class 'Ibexa\Bundle\Commerce\Eshop\Twig\SilvercommonExtension' doesn't have a method 'getNavigation'` > > You can ignore this error, it doesn't require any action on your part. ## Update the app to latest version of v4.0 First, update your application to the latest version of v4.0: v4.0.8. ### Update Flex server The `flex.ibexa.co` Flex server has been disabled. If you're using earlier v4.x versions, and you haven't done it before, you have to update your Flex server. To do it, in your `composer.json`, check whether the `https://flex.ibexa.co` endpoint is still listed in `extra.symfony.endpoint`. If so, replace it with the new [`https://api.github.com/repos/ibexa/recipes/contents/index.json?ref=flex/main`](https://github.com/ibexa/website-skeleton/blob/v4.1.5/composer.json#L96) endpoint. You can do it manually, or by running the following command: ``` composer config extra.symfony.endpoint "https://api.github.com/repos/ibexa/recipes/contents/index.json?ref=flex/main" ``` Perform the update: **Ibexa Content** ``` composer require ibexa/content:4.0.8 --with-all-dependencies --no-scripts composer recipes:install ibexa/content --force -v ``` **Ibexa Experience** ``` composer require ibexa/experience:4.0.8 --with-all-dependencies --no-scripts composer recipes:install ibexa/experience --force -v ``` **Ibexa Commerce** ``` composer require ibexa/commerce:4.0.8 --with-all-dependencies --no-scripts composer recipes:install ibexa/commerce --force -v ``` Next, run: ``` composer run post-install-cmd mysql -u -p < vendor/ibexa/installer/upgrade/db/mysql/ibexa-4.0.3-to-4.0.4.sql ``` ## Update the app to v4.1.0 When you have the v4.0 version, you can update to v4.1.0: **Ibexa Content** ``` composer require ibexa/content:4.1.0 --with-all-dependencies --no-scripts composer recipes:install ibexa/content --force -v ``` **Ibexa Experience** ``` composer require ibexa/experience:4.1.0 --with-all-dependencies --no-scripts composer recipes:install ibexa/experience --force -v ``` **Ibexa Commerce** ``` composer require ibexa/commerce:4.1.0 --with-all-dependencies --no-scripts composer recipes:install ibexa/commerce --force -v ``` The `recipes:install` command installs new YAML configuration files. Look through the old YAML files and move your custom configuration to the relevant new files. Next, run: ``` composer run post-install-cmd ``` ### Update the database > **Caution: Caution** > > Always back up your data before running any database update scripts. > > After updating the database, clear the cache. > > Don't use `--force` argument for `mysql` / `psql` commands when performing update queries. If there is any problem during the update, it's best if the query fails immediately, so you can fix the underlying problem before you execute the update again. If you leave this for later you risk ending up with an incompatible database, though the problems might not surface immediately. Apply the following database update scripts: **MySQL** ``` mysql -u -p < vendor/ibexa/installer/upgrade/db/mysql/ibexa-4.0.0-to-4.1.0.sql ``` **PostgreSQL** ``` psql < vendor/ibexa/installer/upgrade/db/postgresql/ibexa-4.0.0-to-4.1.0.sql ``` #### Ibexa Open Source If you're using Ibexa OSS and have no access to Ibexa DXP's `ibexa/installer` package, database upgrade isn't necessary. ## Update the app to latest version of v4.1 Now, update the application to the latest version of v4.1: 4.1.5. **Ibexa Content** ``` composer require ibexa/content:4.1.5 --with-all-dependencies --no-scripts composer recipes:install ibexa/content --force -v ``` **Ibexa Experience** ``` composer require ibexa/experience:4.1.5 --with-all-dependencies --no-scripts composer recipes:install ibexa/experience --force -v ``` **Ibexa Commerce** ``` composer require ibexa/commerce:4.1.5 --with-all-dependencies --no-scripts composer recipes:install ibexa/commerce --force -v ``` Next, run: ``` composer run post-install-cmd ``` ### Update the database Apply the following database update scripts: **MySQL** ``` mysql -u -p < vendor/ibexa/installer/upgrade/db/mysql/ibexa-4.1.0-to-4.1.1.sql ``` **PostgreSQL** ``` psql < vendor/ibexa/installer/upgrade/db/postgresql/ibexa-4.1.0-to-4.1.1.sql ``` ## Configure the product catalog > **Caution: Caution** > > Always back up your data before you perform any actions on the product catalog. Regardless of whether your application already uses the product catalog or you want to start using this functionality, you can choose to use the old features, present in v4.0.x, or upgrade to the all new product catalog that v4.1.x brings. To use the legacy solution, in the `config/packages` folder, in YAML files with shop configuration, under the `parameters` key, make sure that the `ibexa.commerce.site_access.config.eshop.default.catalog_data_provider` parameter is set to `ez5`. To use the new product catalog, since the new solution doesn't support the old price engine out of the box, in your price engine configuration, you must update the following parameters by providing the `Ibexa\\ProductCatalog\\Bridge\\PriceProvider` value in the `ibexa_setting` table, `commerce` group, `config` identifier: ``` ibexa.commerce.site_access.config.price.default.price_service_chain.basket ibexa.commerce.site_access.config.price.default.price_service_chain.wish_list ibexa.commerce.site_access.config.price.default.price_service_chain.comparison ibexa.commerce.site_access.config.price.default.price_service_chain.wish_list ibexa.commerce.site_access.config.price.default.price_service_chain.comparison ibexa.commerce.site_access.config.price.default.price_service_chain.quick_order ibexa.commerce.site_access.config.price.default.price_service_chain.search_list ibexa.commerce.site_access.config.price.default.price_service_chain.product_list ibexa.commerce.site_access.config.price.default.price_service_chain.stored_basket ibexa.commerce.site_access.config.price.default.price_service_chain.basket_variant ibexa.commerce.site_access.config.price.default.price_service_chain.product_detail ibexa.commerce.site_access.config.price.default.price_service_chain.bestseller_list ibexa.commerce.site_access.config.price.default.price_service_chain.slider_product_list ibexa.commerce.site_access.config.price.default.price_service_chain.quick_order_line_preview ``` You can do it by using the `UPDATE ibexa_setting` command. Example of price engine configuration ``` UPDATE ibexa_setting SET value = '{"ibexa.commerce.site_access.config.basket.default.validHours": 120, "ibexa.commerce.site_access.config.core.default.category_view": "product_list", "ibexa.commerce.site_access.config.core.default.currency_list": {"CAD": "1.55686", "EUR": "1", "GBP": "0.86466", "USD": "1.23625"}, "ibexa.commerce.site_access.config.basket.default.stock_in_column": true, "ibexa.commerce.site_access.config.core.default.shipping_vat_code": "19", "ibexa.commerce.site_access.config.basket.default.description_limit": 50, "ibexa.commerce.site_access.config.core.default.bestseller_threshold": 1, "ibexa.commerce.site_access.config.checkout.de.payment_method.invoice": true, "ibexa.commerce.site_access.config.checkout.en.payment_method.invoice": true, "ibexa.commerce.site_access.config.eshop.default.erp.variant_handling": "SKU_ONLY", "ibexa.commerce.site_access.config.wishlist.default.description_limit": 50, "ibexa.commerce.site_access.config.eshop.default.webconnector.password": "passwo", "ibexa.commerce.site_access.config.eshop.default.webconnector.username": "admin", "ibexa.commerce.site_access.config.checkout.de.shipping_method.standard": true, "ibexa.commerce.site_access.config.checkout.en.shipping_method.standard": true, "ibexa.commerce.site_access.config.core.default.marketing.olark_chat.id": "6295-386-10-7457", "ibexa.commerce.site_access.config.newsletter.default.newsletter_active": false, "ibexa.commerce.site_access.config.basket.default.recalculatePricesAfter": "3 hours", "ibexa.commerce.site_access.config.basket.stored.default.stock_in_column": true, "ibexa.commerce.site_access.config.core.default.currency_rate_changed_at": "01.01.2018", "ibexa.commerce.site_access.config.core.default.template_debitor_country": "DE", "ibexa.commerce.site_access.config.eshop.default.webconnector.erpTimeout": 5, "ibexa.commerce.site_access.config.eshop.default.webconnector.soapTimeout": 5, "ibexa.commerce.site_access.config.basket.stored.default.description_limit": 50, "ibexa.commerce.site_access.config.checkout.default.payment_method.invoice": true, "ibexa.commerce.site_access.config.eshop.default.catalog_description_limit": 50, "ibexa.commerce.site_access.config.newsletter.default.unsubscribe_globally": true, "ibexa.commerce.site_access.config.price.default.price_service_chain.basket": ["Ibexa\\\\ProductCatalog\\\\Bridge\\\\PriceProvider"], "ibexa.commerce.site_access.config.basket.default.refreshCatalogElementAfter": "1 hours", "ibexa.commerce.site_access.config.checkout.default.shipping_method.standard": true, "ibexa.commerce.site_access.config.core.default.enable_customer_number_login": false, "ibexa.commerce.site_access.config.newsletter.default.newsletter2go_auth_key": "", "ibexa.commerce.site_access.config.newsletter.default.newsletter2go_password": "", "ibexa.commerce.site_access.config.newsletter.default.newsletter2go_username": "", "ibexa.commerce.site_access.config.core.default.automatic_currency_conversion": true, "ibexa.commerce.site_access.config.erp.default.web_connector.service_location": "http://webconnproxy.silver-eshop.de?config=harmony_wc3_noop_mapping", "ibexa.commerce.site_access.config.core.default.marketing.olark_chat.activated": false, "ibexa.commerce.site_access.config.price.default.price_service_chain.wish_list": ["Ibexa\\\\ProductCatalog\\\\Bridge\\\\PriceProvider"], "ibexa.commerce.site_access.config.checkout.de.shipping_method.express_delivery": true, "ibexa.commerce.site_access.config.checkout.en.shipping_method.express_delivery": true, "ibexa.commerce.site_access.config.order.management.local.default.shipping_cost": "", "ibexa.commerce.site_access.config.order.management.local.default.shipping_free": "", "ibexa.commerce.site_access.config.price.default.price_service_chain.comparison": ["Ibexa\\\\ProductCatalog\\\\Bridge\\\\PriceProvider"], "ibexa.commerce.site_access.config.core.default.bestseller_limit_on_catalog_page": 6, "ibexa.commerce.site_access.config.price.default.price_service_chain.quick_order": ["Ibexa\\\\ProductCatalog\\\\Bridge\\\\PriceProvider"], "ibexa.commerce.site_access.config.price.default.price_service_chain.search_list": ["Ibexa\\\\ProductCatalog\\\\Bridge\\\\PriceProvider"], "ibexa.commerce.site_access.config.basket.default.additional_text_for_basket_line": false, "ibexa.commerce.site_access.config.core.default.bestseller_limit_in_silver_module": 6, "ibexa.commerce.site_access.config.price.default.price_service_chain.product_list": ["Ibexa\\\\ProductCatalog\\\\Bridge\\\\PriceProvider"], "ibexa.commerce.site_access.config.price.default.price_service_chain.stored_basket": ["Ibexa\\\\ProductCatalog\\\\Bridge\\\\PriceProvider"], "ibexa.commerce.site_access.config.core.de.standard_price_factory.fallback_currency": "EUR", "ibexa.commerce.site_access.config.core.default.bestseller_limit_on_bestseller_page": 6, "ibexa.commerce.site_access.config.core.default.use_template_debitor_contact_number": false, "ibexa.commerce.site_access.config.core.en.standard_price_factory.fallback_currency": "EUR", "ibexa.commerce.site_access.config.price.default.price_service_chain.basket_variant": ["Ibexa\\\\ProductCatalog\\\\Bridge\\\\PriceProvider"], "ibexa.commerce.site_access.config.price.default.price_service_chain.product_detail": ["Ibexa\\\\ProductCatalog\\\\Bridge\\\\PriceProvider"], "ibexa.commerce.site_access.config.checkout.default.shipping_method.express_delivery": false, "ibexa.commerce.site_access.config.core.default.standard_price_factory.base_currency": "EUR", "ibexa.commerce.site_access.config.core.default.use_template_debitor_customer_number": true, "ibexa.commerce.site_access.config.price.default.price_service_chain.bestseller_list": ["Ibexa\\\\ProductCatalog\\\\Bridge\\\\PriceProvider"], "ibexa.commerce.site_access.config.checkout.de.payment_method.paypal_express_checkout": true, "ibexa.commerce.site_access.config.checkout.en.payment_method.paypal_express_checkout": true, "ibexa.commerce.site_access.config.core.default.price_requests_without_customer_number": true, "ibexa.commerce.site_access.config.eshop.default.last_viewed_products_in_session_limit": 10, "ibexa.commerce.site_access.config.basket.default.discontinued_products_listener_active": true, "ibexa.commerce.site_access.config.core.default.standard_price_factory.fallback_currency": "EUR", "ibexa.commerce.site_access.config.price.default.price_service_chain.slider_product_list": ["Ibexa\\\\ProductCatalog\\\\Bridge\\\\PriceProvider"], "ibexa.commerce.site_access.config.checkout.default.order_confirmation.sales_email_address": "", "ibexa.commerce.site_access.config.checkout.default.payment_method.paypal_express_checkout": true, "ibexa.commerce.site_access.config.basket.default.additional_text_for_basket_line_input_limit": 30, "ibexa.commerce.site_access.config.price.default.price_service_chain.quick_order_line_preview": ["Ibexa\\\\ProductCatalog\\\\Bridge\\\\PriceProvider"], "ibexa.commerce.site_access.config.newsletter.default.display_newsletter_box_for_logged_in_users": true, "ibexa.commerce.site_access.config.basket.default.discontinued_products_listener_consider_packaging_unit": true}' WHERE `group` = 'commerce' AND identifier = 'config'; ``` After you update the settings, you can proceed to working with your products. ## Finish update Finish the update process: ``` composer run post-install-cmd ``` Finally, generate the new GraphQL schema: ``` php bin/console ibexa:graphql:generate-schema ``` YAML files with the schema are located in `config/graphql/types/ibexa`. # Update from v4.1.x to v4.2 This update procedure applies if you're using a v4.1 installation. > **Caution: Temporary need of Composerconflict** > > To go through this update, [map the conflicting packages](https://getcomposer.org/doc/04-schema.md#conflict) in your `composer.json` file as following: > > ``` > "conflict": { > "jms/serializer": ">=3.30.0", > "gedmo/doctrine-extensions": ">=3.12.0" > }, > ``` > > These entries can be removed after fully upgrading to v4.6 LTS. ## Update from v4.1.x to v4.1.latest Before you update to v4.2, you need to go through the following steps to update to the latest maintenance release of v4.1 (v4.1.5). ### Update the application Run: **Ibexa Content** ``` composer require ibexa/content:4.1.5 --with-all-dependencies --no-scripts ``` **Ibexa Experience** ``` composer require ibexa/experience:4.1.5 --with-all-dependencies --no-scripts ``` **Ibexa Commerce** ``` composer require ibexa/commerce:4.1.5 --with-all-dependencies --no-scripts ``` ### VCL configuration for Fastly The Fastly `.vcl` configuration files have changed. Follow the upgrade steps below to update them: 1. Locate the `vendor/ibexa/fastly/fastly/ez_main.vcl` file and update your VCL file with the recent changes. 2. Do the same with `vendor/ibexa/fastly/fastly/ez_user_hash.vcl`. 3. Upload a new `snippet_re_enable_shielding.vcl` snippet file, based on `vendor/ibexa/fastly/fastly/snippet_re_enable_shielding.vcl`. Once the VCL configuration has been updated, you may enable [Fastly Shielding](https://docs.fastly.com/en/guides/shielding) if you prefer. ## Update from v4.1.latest to v4.2 When you have the latest version of v4.1, you can update to v4.2. ### Update the application First, run: **Ibexa Content** ``` composer require ibexa/content:4.2.4 --with-all-dependencies --no-scripts composer recipes:install ibexa/content --force -v ``` **Ibexa Experience** ``` composer require ibexa/experience:4.2.4 --with-all-dependencies --no-scripts composer recipes:install ibexa/experience --force -v ``` **Ibexa Commerce** ``` composer require ibexa/commerce:4.2.4 --with-all-dependencies --no-scripts composer recipes:install ibexa/commerce --force -v ``` The `recipes:install` command installs new YAML configuration files. Review the old YAML files and move your custom configuration to the relevant new files. #### Run data migration Next, run data migration required by Product Categories: ``` php bin/console ibexa:migrations:import vendor/ibexa/product-catalog/src/bundle/Resources/migrations/2022_06_23_09_39_product_categories.yaml --name=013_product_categories.yaml ``` If you're using Ibexa Experience or Ibexa Commerce, run data migration required by the Customer portal feature: ``` php bin/console ibexa:migrations:import vendor/ibexa/corporate-account/src/bundle/Resources/migrations/corporate_account.yaml --name=001_corporate_account.yaml ``` If you're using Ibexa Commerce, additionally run: ``` php bin/console ibexa:migrations:import vendor/ibexa/corporate-account/src/bundle/Resources/migrations/corporate_account_commerce.yaml --name=002_corporate_account_commerce.yaml ``` Run `php bin/console ibexa:migrations:migrate -v --dry-run` to ensure that all migrations are ready to be performed. If the dry run is successful, run: ``` php bin/console ibexa:migrations:migrate ``` ### Update the database Next, update the database. > **Caution: Caution** > > Always back up your data before running any database update scripts. > > After updating the database, clear the cache. > > Don't use `--force` argument for `mysql` / `psql` commands when performing update queries. If there is any problem during the update, it's best if the query fails immediately, so you can fix the underlying problem before you execute the update again. If you leave this for later you risk ending up with an incompatible database, though the problems might not surface immediately. Apply the following database update scripts: **MySQL** ``` mysql -u -p < vendor/ibexa/installer/upgrade/db/mysql/ibexa-4.1.latest-to-4.2.0.sql mysql -u -p < vendor/ibexa/installer/upgrade/db/mysql/ibexa-4.2.2-to-4.2.3.sql ``` **PostgreSQL** ``` psql < vendor/ibexa/installer/upgrade/db/postgresql/ibexa-4.1.latest-to-4.2.0.sql psql < vendor/ibexa/installer/upgrade/db/postgresql/ibexa-4.2.2-to-4.2.3.sql ``` #### Ibexa Open Source If you have no access to Ibexa DXP's `ibexa/installer` package, database upgrade isn't necessary. ## Ensure password safety Following [Security advisory: IBEXA-SA-2022-009](https://developers.ibexa.co/security-advisories/ibexa-sa-2022-009-critical-vulnerabilities-in-graphql-role-assignment-ct-editing-and-drafts-tooltips), unless you can verify based on your log files that the vulnerability has not been exploited, you should [revoke passwords](https://doc.ibexa.co/en/latest/users/passwords/#revoking-passwords) for all affected users. ## Remove `node_modules` and `yarn.lock` Next, remove `node_modules` and `yarn.lock` before running `composer run post-update-cmd`, otherwise you can encounter errors during compiling. ``` rm -Rf node_modules rm -Rf yarn.lock ``` # Update from v4.2.x to v4.3 This update procedure applies if you're using a v4.2 installation. > **Caution: Temporary need of Composerconflict** > > To go through this update, [map the conflicting packages](https://getcomposer.org/doc/04-schema.md#conflict) in your `composer.json` file as following: > > ``` > "conflict": { > "jms/serializer": ">=3.30.0", > "gedmo/doctrine-extensions": ">=3.12.0" > }, > ``` > > These entries can be removed after fully upgrading to v4.6 LTS. ## Update from v4.2.x to v4.2.latest Before you update to v4.3, you need to go through the following steps to update to the latest maintenance release of v4.2 (v4.2.4). ### Update the application Run: **Ibexa Content** ``` composer require ibexa/content:4.2.4 --with-all-dependencies --no-scripts ``` **Ibexa Experience** ``` composer require ibexa/experience:4.2.4 --with-all-dependencies --no-scripts ``` **Ibexa Commerce** ``` composer require ibexa/commerce:4.2.4 --with-all-dependencies --no-scripts ``` ## Update from v4.2.latest to v4.3 When you have the latest version of v4.2, you can update to v4.3. ### Update the application First, run: **Ibexa Content** ``` composer require ibexa/content:4.3.5 --with-all-dependencies --no-scripts composer recipes:install ibexa/content --force -v ``` **Ibexa Experience** ``` composer require ibexa/experience:4.3.5 --with-all-dependencies --no-scripts composer recipes:install ibexa/experience --force -v ``` **Ibexa Commerce** ``` composer require ibexa/commerce:4.3.5 --with-all-dependencies --no-scripts composer recipes:install ibexa/commerce --force -v ``` The `recipes:install` command installs new YAML configuration files. Review the old YAML files and move your custom configuration to the relevant new files. ### Run data migration #### Customer Portal self-registration If you're using Ibexa Experience or Ibexa Commerce, run data migration required by the Customer Portal self-registration feature: ``` php bin/console ibexa:migrations:import vendor/ibexa/corporate-account/src/bundle/Resources/migrations/corporate_account_registration.yaml --name=012_corporate_account_registration.yaml ``` #### Migration to `customer` content type This step is required if you have users in your installation that need to be transferred to a new User content type: `customer`. This content type is dedicated to registered frontend customers. This migration is intended for all product versions. If there are no users that are customers in your platform, you can skip this step and move on to [executing migrations](#execute-migrations). ##### Basic migration Use this option to define a user group that should be migrated to a new content type. ``` php bin/console ibexa:migrate:customers --input-user-group=3a3beb3d09ae0dacebf1d324f61bbc34 --create-content-type ``` - `--input-user-group` - represents the remote ID of a user group you want to migrate to a new content type. After migration, this is also the ID of a new Private Customer user group. - `--create-content-type` - if you add this parameter, the system creates the new content type based on the one defined in `--input-user-content-type` ##### Additional parameters Use the parameters below if you need to change a content type name during migration, for example because you already have a `customer` content type, or you want to define different source content type. If you don't have custom User content types, use the basic migration. - `--input-user-content-type` - defines input content type - `--output-user-content-type` - defines output content type - `--user` - defines the user that this command should be executed as, default is Admin - `--batch-limit` - defines data limit for migration of one batch, default value is 25 > **Caution: Caution** > > This improvement prevents logged in backend users from making purchases in the frontend store. #### Execute migrations Run `php bin/console ibexa:migrations:migrate -v --dry-run` to ensure that all migrations are ready to be performed. If the dry run is successful, run the following command to execute the above migrations: ``` php bin/console ibexa:migrations:migrate ``` ### Update the database Next, update the database. > **Caution: Caution** > > Always back up your data before running any database update scripts. > > After updating the database, clear the cache. > > Don't use `--force` argument for `mysql` / `psql` commands when performing update queries. If there is any problem during the update, it's best if the query fails immediately, so you can fix the underlying problem before you execute the update again. If you leave this for later you risk ending up with an incompatible database, though the problems might not surface immediately. Apply the following database update scripts: **MySQL** ``` mysql -u -p < vendor/ibexa/installer/upgrade/db/mysql/ibexa-4.2.latest-to-4.3.0.sql ``` **PostgreSQL** ``` psql < vendor/ibexa/installer/upgrade/db/postgresql/ibexa-4.2.latest-to-4.3.0.sql ``` #### Ibexa Open Source If you have no access to Ibexa DXP's `ibexa/installer` package, database upgrade isn't necessary. ### Clean-up taxonomy database Run the following command for each of your taxonomies to ensure that there are no [content items orphaned during deletion of subtrees](https://doc.ibexa.co/en/latest/content_management/taxonomy/taxonomy/#remove-orphaned-content-items): `php bin/console ibexa:taxonomy:remove-orphaned-content --force` For example: ``` php bin/console ibexa:taxonomy:remove-orphaned-content tags --force php bin/console ibexa:taxonomy:remove-orphaned-content product_categories --force ``` ## Ensure password safety Following [Security advisory: IBEXA-SA-2022-009](https://developers.ibexa.co/security-advisories/ibexa-sa-2022-009-critical-vulnerabilities-in-graphql-role-assignment-ct-editing-and-drafts-tooltips), unless you can verify based on your log files that the vulnerability has not been exploited, you should [revoke passwords](https://doc.ibexa.co/en/latest/users/passwords/#revoking-passwords) for all affected users. ## Finish update Finish the update process: ``` composer run post-install-cmd ``` # Update from v4.3.x to v4.4 This update procedure applies if you're using the newest v4.3 installation. This release deprecates all Commerce packages inIbexa DXP. They will be removed in v5. Until that time, they will be maintained by Ibexa with fixes, including security fixes, but they won't be further developed. Old packages are replaced by [the all-new Ibexa Commerce packages](https://doc.ibexa.co/en/latest/release_notes/ibexa_dxp_v4.4/#all-new-ibexa-commerce-packages). For that reason, there are two update routes you can take. A. If you don't use Commerce functionalities, you can proceed with removing them. B. If you use Commerce functionalities based on the deprecated packages, you can continue to use them for the time being. - [Update with new Commerce packages](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/update_and_migration/from_4.3/update_from_4.3_new_commerce/): Update procedure to v4.4 for people who don't use Commerce packages and can remove them. - [Update with old Commerce packages](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/update_and_migration/from_4.3/update_from_4.3_old_commerce/): Update procedure to v4.4 for people who use deprecated Commerce packages and want to keep them. # Update with new Commerce packages This update procedure applies if you have a v4.3 installation, and you don't use Commerce packages. > **Caution: Temporary need of Composerconflict** > > To go through this update, [map the conflicting packages](https://getcomposer.org/doc/04-schema.md#conflict) in your `composer.json` file as following: > > ``` > "conflict": { > "jms/serializer": ">=3.30.0", > "gedmo/doctrine-extensions": ">=3.12.0" > }, > ``` > > These entries can be removed after fully upgrading to v4.6 LTS. ## Update from v4.3.x to v4.3.latest Before you update to v4.4, you need to go through the following steps to update to the latest maintenance release of v4.3 (v4.3.5). ### Update the application to v4.3.latest Run: **Ibexa Content** ``` composer require ibexa/content:4.3.5 --with-all-dependencies --no-scripts ``` **Ibexa Experience** ``` composer require ibexa/experience:4.3.5 --with-all-dependencies --no-scripts ``` **Ibexa Commerce** ``` composer require ibexa/commerce:4.3.5 --with-all-dependencies --no-scripts ``` ## Remove deprecated field types By default, every v4.3 installation has a set of built-in content types. Some of them use field types deprecated in v4.4, which need to be removed manually. Make sure to remove all occurrences of `sesspecificationstype`, `uivarvarianttype`, `sesselection`, `sesprofiledata` field types from your content types. This step should be performed on the working installation, omitting it results in an error during update: ``` [Ibexa\Core\Persistence\Legacy\Content\FieldValue\Converter\Exception\NotFound (404)] Could not find 'Persistence Field Value Converter' with identifier 'sesspecificationstype' ``` In that case, you can use [Null field type](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/nullfield/index.md) to define a replacement for deprecated field types in `config/services.yaml`: ``` services: ibexa.field_type.sesspecificationstype: class: Ibexa\Core\FieldType\Null\Type arguments: [sesspecificationstype] tags: [{name: ibexa.field_type, alias: sesspecificationstype}] ibexa.field_type.sesspecificationstype.converter: class: Ibexa\Core\Persistence\Legacy\Content\FieldValue\Converter\NullConverter tags: [{name: ibexa.field_type.storage.legacy.converter, alias: sesspecificationstype}] ibexa.field_type.sesspecificationstype.indexable: class: Ibexa\Core\FieldType\Unindexed tags: [{name: ibexa.field_type.indexable, alias: sesspecificationstype}] ibexa.field_type.uivarvarianttype: class: Ibexa\Core\FieldType\Null\Type arguments: [uivarvarianttype] tags: [{name: ibexa.field_type, alias: uivarvarianttype}] ibexa.field_type.uivarvarianttype.converter: class: Ibexa\Core\Persistence\Legacy\Content\FieldValue\Converter\NullConverter tags: [{name: ibexa.field_type.storage.legacy.converter, alias: uivarvarianttype}] ibexa.field_type.uivarvarianttype.indexable: class: Ibexa\Core\FieldType\Unindexed tags: [{name: ibexa.field_type.indexable, alias: uivarvarianttype}] ibexa.field_type.sesselection: class: Ibexa\Core\FieldType\Null\Type arguments: [sesselection] tags: [{name: ibexa.field_type, alias: sesselection}] ibexa.field_type.sesselection.converter: class: Ibexa\Core\Persistence\Legacy\Content\FieldValue\Converter\NullConverter tags: [{name: ibexa.field_type.storage.legacy.converter, alias: sesselection}] ibexa.field_type.sesselection.indexable: class: Ibexa\Core\FieldType\Unindexed tags: [{name: ibexa.field_type.indexable, alias: sesselection}] ibexa.field_type.sesprofiledata: class: Ibexa\Core\FieldType\Null\Type arguments: [sesprofiledata] tags: [{name: ibexa.field_type, alias: sesprofiledata}] ibexa.field_type.sesprofiledata.converter: class: Ibexa\Core\Persistence\Legacy\Content\FieldValue\Converter\NullConverter tags: [{name: ibexa.field_type.storage.legacy.converter, alias: sesprofiledata}] ibexa.field_type.sesprofiledata.indexable: class: Ibexa\Core\FieldType\Unindexed tags: [{name: ibexa.field_type.indexable, alias: sesprofiledata}] ``` ## Update from v4.3.latest to v4.4 When you have the latest version of v4.3, you can update to v4.4. ### Update the application to v4.4 First, run: **Ibexa Content** ``` composer require ibexa/content:4.4.4 --with-all-dependencies --no-scripts composer recipes:install ibexa/content --force -v ``` **Ibexa Experience** ``` composer require ibexa/experience:4.4.4 --with-all-dependencies --no-scripts composer recipes:install ibexa/experience --force -v ``` **Ibexa Commerce** ``` composer require ibexa/commerce:4.4.4 --with-all-dependencies --no-scripts composer recipes:install ibexa/commerce --force -v ``` The `recipes:install` command installs new YAML configuration files. Review the old YAML files and move your custom configuration to the relevant new files ### Flysystem v2 Local adapters' `directory` key changed to `location`. It's defined in `config/packages/oneup_flysystem.yaml`: ``` oneup_flysystem: adapters: default_adapter: local: location: '%kernel.cache_dir%/flysystem' ``` If you haven't applied custom changes to that file, you can reset the third-party `oneup/flysystem-bundle` recipe by executing: ``` composer recipe:install --force --reset -- oneup/flysystem-bundle ``` ### Remove `ibexa/commerce-*` packages with dependencies Remove the following bundles from `config/bundles.php`. You don't have to remove third-party bundles (`FOS\` to `JMS\`) if they're used by your installation. **Ibexa Content** ``` FOS\CommentBundle\FOSCommentBundle Tedivm\StashBundle\TedivmStashBundle WhiteOctober\BreadcrumbsBundle\WhiteOctoberBreadcrumbsBundle Nelmio\SolariumBundle\NelmioSolariumBundle JMS\Payment\CoreBundle\JMSPaymentCoreBundle Joli\ApacheTikaBundle\ApacheTikaBundle JMS\JobQueueBundle\JMSJobQueueBundle FOS\RestBundle\FOSRestBundle JMS\SerializerBundle\JMSSerializerBundle Ibexa\Bundle\Commerce\Eshop\IbexaCommerceEshopBundle Ibexa\Bundle\Commerce\ShopTools\IbexaCommerceShopToolsBundle Ibexa\Bundle\Commerce\Translation\IbexaCommerceTranslationBundle Ibexa\Bundle\Commerce\Payment\IbexaCommercePaymentBundle Ibexa\Bundle\Commerce\Price\IbexaCommercePriceBundle Ibexa\Bundle\Commerce\Tools\IbexaCommerceToolsBundle Ibexa\Bundle\Commerce\Search\IbexaCommerceSearchBundle Ibexa\Bundle\Commerce\PriceEngine\IbexaCommercePriceEngineBundle Ibexa\Bundle\Commerce\SpecificationsType\IbexaCommerceSpecificationsTypeBundle Ibexa\Bundle\Commerce\BaseDesign\IbexaCommerceBaseDesignBundle Ibexa\Bundle\Commerce\FieldTypes\IbexaCommerceFieldTypesBundle Ibexa\Bundle\Commerce\Checkout\IbexaCommerceCheckoutBundle Ibexa\Bundle\Commerce\ShopUi\IbexaCommerceShopUiBundle ``` **Ibexa Experience** ``` FOS\CommentBundle\FOSCommentBundle Tedivm\StashBundle\TedivmStashBundle WhiteOctober\BreadcrumbsBundle\WhiteOctoberBreadcrumbsBundle Nelmio\SolariumBundle\NelmioSolariumBundle JMS\Payment\CoreBundle\JMSPaymentCoreBundle Joli\ApacheTikaBundle\ApacheTikaBundle JMS\JobQueueBundle\JMSJobQueueBundle FOS\RestBundle\FOSRestBundle JMS\SerializerBundle\JMSSerializerBundle Ibexa\Bundle\Commerce\Eshop\IbexaCommerceEshopBundle Ibexa\Bundle\Commerce\ShopTools\IbexaCommerceShopToolsBundle Ibexa\Bundle\Commerce\Translation\IbexaCommerceTranslationBundle Ibexa\Bundle\Commerce\Payment\IbexaCommercePaymentBundle Ibexa\Bundle\Commerce\Price\IbexaCommercePriceBundle Ibexa\Bundle\Commerce\Tools\IbexaCommerceToolsBundle Ibexa\Bundle\Commerce\Search\IbexaCommerceSearchBundle Ibexa\Bundle\Commerce\PriceEngine\IbexaCommercePriceEngineBundle Ibexa\Bundle\Commerce\SpecificationsType\IbexaCommerceSpecificationsTypeBundle Ibexa\Bundle\Commerce\BaseDesign\IbexaCommerceBaseDesignBundle Ibexa\Bundle\Commerce\FieldTypes\IbexaCommerceFieldTypesBundle Ibexa\Bundle\Commerce\Checkout\IbexaCommerceCheckoutBundle Ibexa\Bundle\Commerce\ShopUi\IbexaCommerceShopUiBundle ``` **Ibexa Commerce** ``` FOS\CommentBundle\FOSCommentBundle Tedivm\StashBundle\TedivmStashBundle WhiteOctober\BreadcrumbsBundle\WhiteOctoberBreadcrumbsBundle Nelmio\SolariumBundle\NelmioSolariumBundle JMS\Payment\CoreBundle\JMSPaymentCoreBundle Joli\ApacheTikaBundle\ApacheTikaBundle JMS\JobQueueBundle\JMSJobQueueBundle FOS\RestBundle\FOSRestBundle JMS\SerializerBundle\JMSSerializerBundle Ibexa\Bundle\Commerce\Eshop\IbexaCommerceEshopBundle Ibexa\Bundle\Commerce\ShopTools\IbexaCommerceShopToolsBundle Ibexa\Bundle\Commerce\Translation\IbexaCommerceTranslationBundle Ibexa\Bundle\Commerce\Payment\IbexaCommercePaymentBundle Ibexa\Bundle\Commerce\Price\IbexaCommercePriceBundle Ibexa\Bundle\Commerce\Tools\IbexaCommerceToolsBundle Ibexa\Bundle\Commerce\Search\IbexaCommerceSearchBundle Ibexa\Bundle\Commerce\PriceEngine\IbexaCommercePriceEngineBundle Ibexa\Bundle\Commerce\SpecificationsType\IbexaCommerceSpecificationsTypeBundle Ibexa\Bundle\Commerce\BaseDesign\IbexaCommerceBaseDesignBundle Ibexa\Bundle\Commerce\FieldTypes\IbexaCommerceFieldTypesBundle Ibexa\Bundle\Commerce\Checkout\IbexaCommerceCheckoutBundle Ibexa\Bundle\Commerce\ShopUi\IbexaCommerceShopUiBundle # ... Ibexa\Bundle\Commerce\OneSky\IbexaCommerceOneSkyBundle Ibexa\Bundle\Commerce\EzStudio\IbexaCommerceEzStudioBundle Ibexa\Bundle\Commerce\Comparison\IbexaCommerceComparisonBundle Ibexa\Bundle\Commerce\QuickOrder\IbexaCommerceQuickOrderBundle Ibexa\Bundle\Commerce\TestTools\IbexaCommerceTestToolsBundle Ibexa\Bundle\Commerce\Voucher\IbexaCommerceVoucherBundle Ibexa\Bundle\Commerce\LocalOrderManagement\IbexaCommerceLocalOrderManagementBundle Ibexa\Bundle\Commerce\Newsletter\IbexaCommerceNewsletterBundle Ibexa\Bundle\Commerce\OrderHistory\IbexaCommerceOrderHistoryBundle Ibexa\Bundle\Commerce\ErpAdmin\IbexaCommerceErpAdminBundle Ibexa\Bundle\Commerce\ShopFrontend\IbexaCommerceShopFrontendBundle Ibexa\Bundle\Commerce\Basket\IbexaCommerceBasketBundle::class Ibexa\Bundle\Commerce\Rest\IbexaCommerceRestBundle::class Ibexa\Bundle\Commerce\AdminUi\IbexaCommerceAdminUiBundle::class Ibexa\Bundle\Commerce\PageBuilder\IbexaCommercePageBuilderBundle::class EWZ\Bundle\RecaptchaBundle\EWZRecaptchaBundle::class ``` Next, remove related extensions' configuration. You don't have to remove third-party bundles (for example `config/packages/fos_rest.yaml`) if they're used by your installation. **Ibexa Content** ``` config/packages/commerce.yaml config/packages/commerce/autogenerated/.gitkeep config/packages/commerce/commerce.yaml config/packages/commerce/commerce_advanced.yaml config/packages/commerce/commerce_common.yaml config/packages/commerce/commerce_demo.yaml config/packages/commerce/commerce_parameters.yaml config/packages/nelmio_solarium.yaml ``` **Ibexa Experience** ``` config/packages/commerce.yaml config/packages/commerce/autogenerated/.gitkeep config/packages/commerce/commerce.yaml config/packages/commerce/commerce_advanced.yaml config/packages/commerce/commerce_common.yaml config/packages/commerce/commerce_demo.yaml config/packages/commerce/commerce_parameters.yaml config/packages/nelmio_solarium.yaml ``` **Ibexa Commerce** ``` config/packages/commerce.yaml config/packages/commerce/autogenerated/.gitkeep config/packages/commerce/commerce.yaml config/packages/commerce/commerce_advanced.yaml config/packages/commerce/commerce_common.yaml config/packages/commerce/commerce_demo.yaml config/packages/commerce/commerce_parameters.yaml config/packages/dev/ewz_recaptcha.yaml config/packages/dev/jms_serializer.yaml config/packages/ewz_recaptcha.yaml config/packages/ezcommerce/autogenerated/commerce_repository_parameters.yaml config/packages/fos_rest.yaml config/packages/google_recaptcha.yaml config/packages/jms_serializer.yaml config/packages/nelmio_solarium.yaml config/packages/prod/jms_serializer.yaml ``` Finally, remove related routes by deleting `config/routes/ibexa_commerce.yaml` file. ### Update the database Next, update the database if you're using Ibexa Commerce. Ibexa Content and Ibexa Experience don't require the database update. > **Caution: Caution** > > Always back up your data before running any database update scripts. > > After updating the database, clear the cache. > > Don't use `--force` argument for `mysql` / `psql` commands when performing update queries. If there is any problem during the update, it's best if the query fails immediately, so you can fix the underlying problem before you execute the update again. If you leave this for later you risk ending up with an incompatible database, though the problems might not surface immediately. Apply the following database update scripts: **MySQL** ``` mysql -u -p < vendor/ibexa/installer/upgrade/db/mysql/commerce/ibexa-4.3.latest-to-4.4.0.sql ``` **PostgreSQL** ``` psql < vendor/ibexa/installer/upgrade/db/postgresql/commerce/ibexa-4.3.latest-to-4.4.0.sql ``` If you used old Commerce packages before, and have migrated everything, you can remove the old tables. The tables that can be removed are prefixed with `ses_` and `sve_`. **MySQL** To switch to the right database, issue the following command: ``` USE ; ``` Then, to remove all the old tables, run the following queries: ``` DROP TABLE IF EXISTS ses_basket; DROP TABLE IF EXISTS ses_basket_line; DROP TABLE IF EXISTS ses_content_modification_queue; DROP TABLE IF EXISTS ses_customer_prices; DROP TABLE IF EXISTS ses_customer_sku; DROP TABLE IF EXISTS ses_download; DROP TABLE IF EXISTS ses_externaldata; DROP TABLE IF EXISTS ses_gdpr_log; DROP TABLE IF EXISTS ses_invoice; DROP TABLE IF EXISTS ses_log_erp; DROP TABLE IF EXISTS ses_log_mail; DROP TABLE IF EXISTS ses_log_search; DROP TABLE IF EXISTS ses_payment_basket_map; DROP TABLE IF EXISTS ses_price; DROP TABLE IF EXISTS ses_shipping_cost; DROP TABLE IF EXISTS ses_stat_sessions; DROP TABLE IF EXISTS ses_stock; DROP TABLE IF EXISTS ses_token; DROP TABLE IF EXISTS sve_class; DROP TABLE IF EXISTS sve_class_attributes; DROP TABLE IF EXISTS sve_object; DROP TABLE IF EXISTS sve_object_attributes; DROP TABLE IF EXISTS sve_object_attributes_tmp; DROP TABLE IF EXISTS sve_object_catalog; DROP TABLE IF EXISTS sve_object_catalog_tmp; DROP TABLE IF EXISTS sve_object_tmp; DROP TABLE IF EXISTS sve_object_urls; DROP TABLE IF EXISTS sve_object_urls_tmp; ``` **PostgreSQL** To switch to the right database, issue the following command: ``` \connect ; ``` Then, to remove all the old tables, run the following queries: ``` DROP TABLE IF EXISTS ses_basket; DROP TABLE IF EXISTS ses_basket_line; DROP TABLE IF EXISTS ses_content_modification_queue; DROP TABLE IF EXISTS ses_customer_prices; DROP TABLE IF EXISTS ses_customer_sku; DROP TABLE IF EXISTS ses_download; DROP TABLE IF EXISTS ses_externaldata; DROP TABLE IF EXISTS ses_gdpr_log; DROP TABLE IF EXISTS ses_invoice; DROP TABLE IF EXISTS ses_log_erp; DROP TABLE IF EXISTS ses_log_mail; DROP TABLE IF EXISTS ses_log_search; DROP TABLE IF EXISTS ses_payment_basket_map; DROP TABLE IF EXISTS ses_price; DROP TABLE IF EXISTS ses_shipping_cost; DROP TABLE IF EXISTS ses_stat_sessions; DROP TABLE IF EXISTS ses_stock; DROP TABLE IF EXISTS ses_token; DROP TABLE IF EXISTS sve_class; DROP TABLE IF EXISTS sve_class_attributes; DROP TABLE IF EXISTS sve_object; DROP TABLE IF EXISTS sve_object_attributes; DROP TABLE IF EXISTS sve_object_attributes_tmp; DROP TABLE IF EXISTS sve_object_catalog; DROP TABLE IF EXISTS sve_object_catalog_tmp; DROP TABLE IF EXISTS sve_object_tmp; DROP TABLE IF EXISTS sve_object_urls; DROP TABLE IF EXISTS sve_object_urls_tmp; ``` #### Ibexa Open Source If you have no access to Ibexa DXP's `ibexa/installer` package, database upgrade isn't necessary. ## Ensure password safety Following [Security advisory: IBEXA-SA-2022-009](https://developers.ibexa.co/security-advisories/ibexa-sa-2022-009-critical-vulnerabilities-in-graphql-role-assignment-ct-editing-and-drafts-tooltips), unless you can verify based on your log files that the vulnerability has not been exploited, you should [revoke passwords](https://doc.ibexa.co/en/latest/users/passwords/#revoking-passwords) for all affected users. ## Finish code update Finish the code update by running: ``` composer run post-install-cmd ``` ## Run data migration ### Customer Portal self-registration If you're using Ibexa Experience or Ibexa Commerce, you can now run data migration required by the Customer Portal applications feature to finish the update process: ``` php bin/console ibexa:migrations:import vendor/ibexa/corporate-account/src/bundle/Resources/migrations/application_internal_fields.yaml --name=2022_11_07_22_46_application_internal_fields.yaml php bin/console ibexa:migrations:migrate --file=2022_11_07_22_46_application_internal_fields.yaml ``` # Update with old Commerce packages This update procedure applies if you have a v4.3 installation, you use Commerce packages and would like to continue to use them. All commerce packages as of v4.4 are deprecated and will be removed in v5. Until that time, they will be maintained by Ibexa with fixes, including security fixes, but they won't be further developed. Old packages are replaced by [the all-new Ibexa Commerce packages](https://doc.ibexa.co/en/latest/release_notes/ibexa_dxp_v4.4/#all-new-ibexa-commerce-packages). > **Caution: Temporary need of Composerconflict** > > To go through this update, [map the conflicting packages](https://getcomposer.org/doc/04-schema.md#conflict) in your `composer.json` file as following: > > ``` > "conflict": { > "jms/serializer": ">=3.30.0", > "gedmo/doctrine-extensions": ">=3.12.0" > }, > ``` > > These entries can be removed after fully upgrading to v4.6 LTS. ## Update from v4.3.x to v4.3.latest Before you update to v4.4, you need to go through the following steps to update to the latest maintenance release of v4.3 (v4.3.5). ### Update the application to v4.3.latest Run: **Ibexa Content** ``` composer require ibexa/content:4.3.5 --with-all-dependencies --no-scripts ``` **Ibexa Experience** ``` composer require ibexa/experience:4.3.5 --with-all-dependencies --no-scripts ``` **Ibexa Commerce** ``` composer require ibexa/commerce:4.3.5 --with-all-dependencies --no-scripts ``` ## Update from v4.3.latest to v4.4 When you have the latest version of v4.3, you can update to v4.4. ### Update the application to v4.4 First, run: **Ibexa Content** ``` composer require ibexa/content:4.4.4 --with-all-dependencies --no-scripts composer recipes:install ibexa/content --force -v ``` **Ibexa Experience** ``` composer require ibexa/experience:4.4.4 --with-all-dependencies --no-scripts composer recipes:install ibexa/experience --force -v ``` **Ibexa Commerce** ``` composer require ibexa/commerce:4.4.4 --with-all-dependencies --no-scripts composer recipes:install ibexa/commerce --force -v ``` The `recipes:install` command installs new YAML configuration files. Review the old YAML files and move your custom configuration to the relevant new files. #### Flysystem v2 Local adapters' `directory` key changed to `location`. It's defined in `config/packages/oneup_flysystem.yaml`: ``` oneup_flysystem: adapters: default_adapter: local: location: '%kernel.cache_dir%/flysystem' ``` If you haven't applied custom changes to that file, you can reset third-party `oneup/flysystem-bundle` recipe by executing: ``` composer recipe:install --force --reset -- oneup/flysystem-bundle ``` ### Add `ibexa/commerce-*` packages dependencies Add the following dependencies in the `require` section in `composer.json`: **Ibexa Content** ``` "require":{ "ibexa/commerce-base-design": "4.4.0", "ibexa/commerce-checkout": "4.4.0", "ibexa/commerce-fieldtypes": "4.4.0", "ibexa/commerce-price-engine": "4.4.0", "ibexa/commerce-shop": "4.4.0", "ibexa/commerce-shop-ui": "4.4.0", "ezsystems/apache-tika-bundle": "^2.0", "ezsystems/comment-bundle": "^3.1", "ezsystems/job-queue-bundle": "^4.0", "ezsystems/payment-core-bundle": "^3.0", "ezsystems/stash-bundle": "^0.9", } ``` **Ibexa Experience** ``` "require":{ "ibexa/commerce-base-design": "4.4.0", "ibexa/commerce-checkout": "4.4.0", "ibexa/commerce-fieldtypes": "4.4.0", "ibexa/commerce-price-engine": "4.4.0", "ibexa/commerce-shop": "4.4.0", "ibexa/commerce-shop-ui": "4.4.0", "ezsystems/apache-tika-bundle": "^2.0", "ezsystems/comment-bundle": "^3.1", "ezsystems/job-queue-bundle": "^4.0", "ezsystems/payment-core-bundle": "^3.0", "ezsystems/stash-bundle": "^0.9", } ``` **Ibexa Commerce** ``` "require":{ "ibexa/commerce-base-design": "4.4.0", "ibexa/commerce-checkout": "4.4.0", "ibexa/commerce-fieldtypes": "4.4.0", "ibexa/commerce-price-engine": "4.4.0", "ibexa/commerce-shop": "4.4.0", "ibexa/commerce-shop-ui": "4.4.0", "ezsystems/apache-tika-bundle": "^2.0", "ezsystems/comment-bundle": "^3.1", "ezsystems/job-queue-bundle": "^4.0", "ezsystems/payment-core-bundle": "^3.0", "ezsystems/stash-bundle": "^0.9", "ibexa/commerce-admin-ui": "4.4.0", "ibexa/commerce-erp-admin": "4.4.0", "ibexa/commerce-order-history": "4.4.0", "ibexa/commerce-page-builder": "4.4.0", "ibexa/commerce-rest": "4.4.0", "ibexa/commerce-transaction": "4.4.0" } ``` Next, remove the entries with new packages alongside with routing and configuration in `config/routes/ibexa_cart.yaml`, `config/routes/ibexa_checkout.yaml` and `config/routes/ibexa_storefront.yaml`: ``` Ibexa\Bundle\Cart\IbexaCartBundle::class => ['all' => true], Ibexa\Bundle\Checkout\IbexaCheckoutBundle::class => ['all' => true], Ibexa\Bundle\Storefront\IbexaStorefrontBundle::class => ['all' => true], ``` Finally, remove the new `storefront_group` SiteAccess from `config/packages/ibexa.yaml`: ``` ibexa: siteaccess: groups: site_group: [import, site] storefront_group: [site] ``` ### Update the database Next, update the database if you're using Ibexa Commerce. Ibexa Content and Ibexa Experience don't require the database update. > **Caution: Caution** > > Always back up your data before running any database update scripts. > > After updating the database, clear the cache. > > Don't use `--force` argument for `mysql` / `psql` commands when performing update queries. If there is any problem during the update, it's best if the query fails immediately, so you can fix the underlying problem before you execute the update again. If you leave this for later you risk ending up with an incompatible database, though the problems might not surface immediately. Apply the following database update scripts: **MySQL** ``` mysql -u -p < vendor/ibexa/installer/upgrade/db/mysql/commerce/ibexa-4.3.latest-to-4.4.0.sql ``` **PostgreSQL** ``` psql < vendor/ibexa/installer/upgrade/db/postgresql/commerce/ibexa-4.3.latest-to-4.4.0.sql ``` #### Ibexa Open Source If you have no access to Ibexa DXP's `ibexa/installer` package, database upgrade isn't necessary. ## Ensure password safety Following [Security advisory: IBEXA-SA-2022-009](https://developers.ibexa.co/security-advisories/ibexa-sa-2022-009-critical-vulnerabilities-in-graphql-role-assignment-ct-editing-and-drafts-tooltips), unless you can verify based on your log files that the vulnerability has not been exploited, you should [revoke passwords](https://doc.ibexa.co/en/latest/users/passwords/#revoking-passwords) for all affected users. ## Finish code update Finish the code update by running: ``` composer run post-install-cmd ``` ## Run data migration ### Customer Portal self-registration If you're using Ibexa Experience or Ibexa Commerce, run data migration required by the Customer Portal applications feature: ``` php bin/console ibexa:migrations:import vendor/ibexa/corporate-account/src/bundle/Resources/migrations/application_internal_fields.yaml --name=2022_11_07_22_46_application_internal_fields.yaml php bin/console ibexa:migrations:migrate --file=2022_11_07_22_46_application_internal_fields.yaml ``` # Update from v4.4.x to v4.5 This update procedure applies if you're using a v4.4 installation. ## Update from v4.4.x to v4.4.latest Before you update to v4.5, you need to go through the following steps to update to the latest maintenance release of v4.4 (v4.4.4). ### Update the application to v4.4.latest > **Caution: Temporary need of Composerconflict** > > To go through this update, [map the conflicting packages](https://getcomposer.org/doc/04-schema.md#conflict) in your `composer.json` file as following: > > ``` > "conflict": { > "jms/serializer": ">=3.30.0", > "gedmo/doctrine-extensions": ">=3.12.0" > }, > ``` > > These entries can be removed after fully upgrading to v4.6 LTS. Run: **Ibexa Content** ``` composer require ibexa/content:4.4.4 --with-all-dependencies --no-scripts ``` **Ibexa Experience** ``` composer require ibexa/experience:4.4.4 --with-all-dependencies --no-scripts ``` **Ibexa Commerce** ``` composer require ibexa/commerce:4.4.4 --with-all-dependencies --no-scripts ``` > **Note: Remove temporary Composerconflict** > > You can now remove the temporary Composer `conflict` entries from your `composer.json` file: > > ``` > "conflict": { > - "jms/serializer": ">=3.30.0", > - "gedmo/doctrine-extensions": ">=3.12.0" > }, > ``` ## Update from v4.4.latest to v4.5 When you have the latest version of v4.4, you can update to v4.5. ### Update the application First, run: **Ibexa Content** ``` composer require ibexa/content:4.5.7 --with-all-dependencies --no-scripts composer recipes:install ibexa/content --force -v ``` **Ibexa Experience** ``` composer require ibexa/experience:4.5.7 --with-all-dependencies --no-scripts composer recipes:install ibexa/experience --force -v ``` **Ibexa Commerce** ``` composer require ibexa/commerce:4.5.7 --with-all-dependencies --no-scripts composer recipes:install ibexa/commerce --force -v ``` The `recipes:install` command installs new YAML configuration files. Review the old YAML files and move your custom configuration to the relevant new files. ### Define measurement base unit in configuration If your installation has defined measurement units in the configuration, you need to specify one of them as base unit in the `config/packages/ibexa_measurement.yaml` file: ``` ibexa_measurement: types: my_type: my_unit: { symbol: my, is_base_unit: true } ``` Next, add unit conversion to `src/bundle/Resources/config/services/conversion.yaml`. For more information, see [Modify and add Measurement types and units](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/measurementfield/#modify-and-add-measurement-types-and-units). ### Update the database Next, update the database: > **Caution: Caution** > > Always back up your data before running any database update scripts. > > After updating the database, clear the cache. > > Don't use `--force` argument for `mysql` / `psql` commands when performing update queries. If there is any problem during the update, it's best if the query fails immediately, so you can fix the underlying problem before you execute the update again. If you leave this for later you risk ending up with an incompatible database, though the problems might not surface immediately. Apply the following database update scripts: **MySQL** ``` mysql -u -p < vendor/ibexa/installer/upgrade/db/mysql/ibexa-4.4.latest-to-4.5.0.sql ``` **PostgreSQL** ``` psql < vendor/ibexa/installer/upgrade/db/postgresql/ibexa-4.4.latest-to-4.5.0.sql ``` #### Migrate richtext namespaces If you earlier upgraded from v3.3 to v4.x and haven't run the migrate script yet, do it now, run: ``` php bin/console ibexa:migrate:richtext-namespaces ``` #### Ibexa Open Source If you have no access to Ibexa DXP's `ibexa/installer` package, apply the following database update: **MySQL** ``` CREATE TABLE ibexa_token_type ( id int(11) NOT NULL AUTO_INCREMENT, identifier varchar(64) NOT NULL, PRIMARY KEY (id), UNIQUE KEY ibexa_token_type_unique (identifier) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; CREATE TABLE ibexa_token ( id int(11) NOT NULL AUTO_INCREMENT, type_id int(11) NOT NULL, token varchar(255) NOT NULL, identifier varchar(128) DEFAULT NULL, created int(11) NOT NULL DEFAULT 0, expires int(11) NOT NULL DEFAULT 0, PRIMARY KEY (id), UNIQUE KEY ibexa_token_unique (token,identifier,type_id), CONSTRAINT ibexa_token_type_id_fk FOREIGN KEY (type_id) REFERENCES ibexa_token_type (id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; ``` **PostgreSQL** ``` CREATE TABLE ibexa_token_type ( id serial PRIMARY KEY, identifier varchar(64) NOT NULL ); CREATE TABLE ibexa_token ( id serial PRIMARY KEY, type_id int NOT NULL CONSTRAINT ibexa_token_type_id_fk REFERENCES ibexa_token_type (id) ON DELETE CASCADE, token varchar(255) NOT NULL, identifier varchar(128) DEFAULT NULL, created int NOT NULL DEFAULT 0, expires int NOT NULL DEFAULT 0 ); ``` ### Clean-up taxonomy database If you didn't run it already when [migrating from 4.2 to 4.3](https://doc.ibexa.co/en/latest/update_and_migration/from_4.2/update_from_4.2/#clean-up-taxonomy-database), run the following command for each of your taxonomies to ensure that there are no [content items orphaned during deletion of subtrees](https://doc.ibexa.co/en/latest/content_management/taxonomy/taxonomy/#remove-orphaned-content-items) inherited from the earlier version's database: `php bin/console ibexa:taxonomy:remove-orphaned-content --force` For example: ``` php bin/console ibexa:taxonomy:remove-orphaned-content tags --force php bin/console ibexa:taxonomy:remove-orphaned-content product_categories --force ``` ## Finish code update Finish the code update by running: ``` composer run post-install-cmd ``` ## Run data migration If you're using Ibexa Experience or Ibexa Commerce, you can now run data migration required by the Customer Portal and Commerce features to finish the update process: - Customer Portal (Experience) (Commerce) ``` php bin/console ibexa:migrations:import vendor/ibexa/corporate-account/src/bundle/Resources/migrations/customer_portal.yaml --name=2023_03_06_13_00_customer_portal.yaml php bin/console ibexa:migrations:migrate --file=2023_03_06_13_00_customer_portal.yaml ``` - Corporate access role update (Experience) (Commerce) ``` php bin/console ibexa:migrations:import vendor/ibexa/corporate-account/src/bundle/Resources/migrations/2023_05_09_12_40_corporate_access_role_update.yaml --name=2023_05_09_12_40_corporate_access_role_update.yaml php bin/console ibexa:migrations:migrate --file=2023_05_09_12_40_corporate_access_role_update.yaml ``` - Corporate account (Commerce) This migration allows all company members to shop in the frontend shop. If you have implemented business logic that depends on keeping company members out of the frontend shop, you can skip it: ``` php bin/console ibexa:migrations:import vendor/ibexa/storefront/src/bundle/Resources/migrations/2023_04_27_10_30_corporate_account.yaml --name=2023_04_27_10_30_corporate_account.yaml php bin/console ibexa:migrations:migrate --file=2023_04_27_10_30_corporate_account.yaml ``` - Storefront user update (Commerce) ``` php bin/console ibexa:migrations:import vendor/ibexa/storefront/src/bundle/Resources/migrations/2023_04_27_11_20_storefront_user_role_update.yaml --name=2023_04_27_11_20_storefront_user_role_update.yaml php bin/console ibexa:migrations:migrate --file=2023_04_27_11_20_storefront_user_role_update.yaml ``` - Shipment permissions (Commerce) ``` php bin/console ibexa:migrations:import vendor/ibexa/shipping/src/bundle/Resources/install/migrations/shipment_permissions.yaml --name=shipment_permissions.yaml php bin/console ibexa:migrations:migrate --file=shipment_permissions.yaml ``` - Order permissions (Commerce) ``` php bin/console ibexa:migrations:import vendor/ibexa/order-management/src/bundle/Resources/install/migrations/order_permissions.yaml --name=order_permissions.yaml php bin/console ibexa:migrations:migrate --file=order_permissions.yaml ``` ## Update to v4.5.latest You can now continue applying the instructions for the 4.5 patch releases, starting with [v4.5.2](https://doc.ibexa.co/en/latest/update_and_migration/from_4.5/update_from_4.5/#v452). # Update from v4.5.x to v4.6 This update procedure applies if you're using a v4.5 installation. ## Update from v4.5.x to v4.5.latest Before you update to v4.6, you need to go through the following steps to update to the latest maintenance release of v4.5 (v4.5.7). Note which version you actually have before starting. ### Update the application to v4.5.latest Run: **Ibexa Content** ``` composer require ibexa/content:4.5.7 --with-all-dependencies --no-scripts ``` **Ibexa Experience** ``` composer require ibexa/experience:4.5.7 --with-all-dependencies --no-scripts ``` **Ibexa Commerce** ``` composer require ibexa/commerce:4.5.7 --with-all-dependencies --no-scripts ``` ### v4.5.2 #### Database update Run the following scripts: **MySQL** ``` mysql -u -p < vendor/ibexa/installer/upgrade/db/mysql/ibexa-4.5.1-to-4.5.2.sql ``` **PostgreSQL** ``` psql < vendor/ibexa/installer/upgrade/db/postgresql/ibexa-4.5.1-to-4.5.2.sql ``` ### v4.5.3 #### Database update (Experience) (Commerce) Run the following scripts: **MySQL** ``` mysql -u -p < vendor/ibexa/installer/upgrade/db/mysql/ibexa-4.5.2-to-4.5.3.sql ``` **PostgreSQL** ``` psql < vendor/ibexa/installer/upgrade/db/postgresql/ibexa-4.5.2-to-4.5.3.sql ``` ### v4.5.4 #### Database update Run the following scripts: **MySQL** ``` mysql -u -p < vendor/ibexa/installer/upgrade/db/mysql/ibexa-4.5.3-to-4.5.4.sql ``` **PostgreSQL** ``` psql < vendor/ibexa/installer/upgrade/db/postgresql/ibexa-4.5.3-to-4.5.4.sql ``` ### v4.5.5 No additional steps needed. ### v4.5.6 #### Database update Run the following scripts: **MySQL** ``` mysql -u -p < vendor/ibexa/installer/upgrade/db/mysql/ibexa-4.5.5-to-4.5.6.sql ``` **PostgreSQL** ``` psql < vendor/ibexa/installer/upgrade/db/postgresql/ibexa-4.5.5-to-4.5.6.sql ``` ### v4.5.7 No additional steps needed. ## Update from v4.5.latest to v4.6 When you have the latest version of v4.5, you can update to v4.6. Check [the requirements](https://doc.ibexa.co/en/latest/getting_started/requirements/index.md) first. This version adds support for PHP 8.2 and 8.3, but requires using at least Node 18. ### Update the application First, run: **Ibexa Headless (formerly Ibexa Content)** ``` composer remove ibexa/content --no-update --no-scripts # Avoid recipes conflict between configuring ibexa/headless and unconfiguring ibexa/content rm symfony.lock composer require ibexa/headless:4.6.29 --with-all-dependencies --no-scripts composer recipes:install ibexa/headless --force -v # Update CKEditor dependencies yarn add @ckeditor/ckeditor5-alignment@^40.1.0 @ckeditor/ckeditor5-build-inline@^40.1.0 @ckeditor/ckeditor5-dev-utils@^39.0.0 @ckeditor/ckeditor5-widget@^40.1.0 @ckeditor/ckeditor5-theme-lark@^40.1.0 @ckeditor/ckeditor5-code-block@^40.1.0 ``` **Ibexa Experience** ``` composer require ibexa/experience:4.6.29 --with-all-dependencies --no-scripts composer recipes:install ibexa/experience --force -v # Update CKEditor dependencies yarn add @ckeditor/ckeditor5-alignment@^40.1.0 @ckeditor/ckeditor5-build-inline@^40.1.0 @ckeditor/ckeditor5-dev-utils@^39.0.0 @ckeditor/ckeditor5-widget@^40.1.0 @ckeditor/ckeditor5-theme-lark@^40.1.0 @ckeditor/ckeditor5-code-block@^40.1.0 ``` **Ibexa Commerce** ``` composer require ibexa/commerce:4.6.29 --with-all-dependencies --no-scripts composer recipes:install ibexa/commerce --force -v # Update CKEditor dependencies yarn add @ckeditor/ckeditor5-alignment@^40.1.0 @ckeditor/ckeditor5-build-inline@^40.1.0 @ckeditor/ckeditor5-dev-utils@^39.0.0 @ckeditor/ckeditor5-widget@^40.1.0 @ckeditor/ckeditor5-theme-lark@^40.1.0 @ckeditor/ckeditor5-code-block@^40.1.0 ``` The `recipes:install` command installs new YAML configuration files. Review the old YAML files and move your custom configuration to the relevant new files. If you're using [custom CKEditor plugins](https://doc.ibexa.co/en/latest/content_management/rich_text/extend_online_editor/#add-ckeditor-plugins), update them as well to use the same version range for all CKEditor dependencies. ## Remove `node_modules` and `yarn.lock` Next, remove `node_modules` and `yarn.lock` before running `composer run post-update-cmd`, otherwise you can encounter errors during compiling. ``` rm -Rf node_modules rm yarn.lock ``` ## Finish code update Finish the code update by running: ``` composer run post-install-cmd ``` ### Known issues You may encounter one of the following errors during the process. #### Non-existent parameter If you encounter a `You have requested a non-existent parameter` error (like, for example, `You have requested a non-existent parameter "ibexa.dashboard.ibexa_news.limit".`), this is due to incorrect order of entries in `config/bundles.php`. To fix this, use the order from the skeleton you're using, and add any extra bundles again. **Ibexa Headless** Use as a reference. **Ibexa Experience** Use as a reference. **Ibexa Commerce** Use as a reference. #### Non-existent service If you encounter the `You have requested a non-existent service "payum.storage.doctrine.orm".` error, replace the config/packages/payum.yaml file with the contents from . ## Update the database Next, update the database: > **Caution: Caution** > > Always back up your data before running any database update scripts. > > After updating the database, clear the cache. > > Don't use `--force` argument for `mysql` / `psql` commands when performing update queries. If there is any problem during the update, it's best if the query fails immediately, so you can fix the underlying problem before you execute the update again. If you leave this for later you risk ending up with an incompatible database, though the problems might not surface immediately. Apply the following database update scripts: **MySQL** ``` mysql -u -p < vendor/ibexa/installer/upgrade/db/mysql/ibexa-4.5.latest-to-4.6.0.sql ``` **PostgreSQL** ``` psql < vendor/ibexa/installer/upgrade/db/postgresql/ibexa-4.5.latest-to-4.6.0.sql ``` ### Update Ibexa Commerce database (Commerce) For Ibexa Commerce installations, you also need to run the following command line: **MySQL** ``` mysql -u -p < vendor/ibexa/installer/upgrade/db/mysql/commerce/ibexa-4.5.latest-to-4.6.0.sql ``` **PostgreSQL** ``` psql < vendor/ibexa/installer/upgrade/db/postgresql/commerce/ibexa-4.5.latest-to-4.6.0.sql ``` And apply the following database script: **MySQL** ``` CREATE TABLE ibexa_payment_token ( hash VARCHAR(255) NOT NULL, afterUrl VARCHAR(255) DEFAULT NULL, targetUrl VARCHAR(255) NOT NULL, gatewayName VARCHAR(255) NOT NULL, details LONGTEXT DEFAULT NULL COMMENT '(DC2Type:object)', PRIMARY KEY(hash) ) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_520_ci` ENGINE = InnoDB; ``` **PostgreSQL** ``` CREATE TABLE ibexa_payment_token ( hash VARCHAR(255) NOT NULL, afterurl VARCHAR(255) DEFAULT NULL, targeturl VARCHAR(255) NOT NULL, gatewayname VARCHAR(255) NOT NULL, details TEXT DEFAULT NULL, PRIMARY KEY(hash) ); COMMENT ON COLUMN ibexa_payment_token.details IS '(DC2Type:object)'; ``` ## Run data migration ### Image picker migration The new Image picker by default expects an `ezkeyword` field type to exist in the `image` content type. You can add it running the following commands: ``` php bin/console ibexa:migrations:import vendor/ibexa/image-picker/src/bundle/Resources/migrations/2023_12_06_15_00_image_content_type.yaml --name=2023_12_06_15_00_image_content_type.yaml php bin/console ibexa:migrations:migrate --file=2023_12_06_15_00_image_content_type.yaml ``` ### Dashboard migration (Experience) (Commerce) If you're using Ibexa Experience or Ibexa Commerce, you must run data migration required by the dashboard and other features to finish the upgrade process: ``` php bin/console ibexa:migrations:import vendor/ibexa/dashboard/src/bundle/Resources/migrations/structure.yaml --name=2023_09_23_14_15_dashboard_structure.yaml php bin/console ibexa:migrations:import vendor/ibexa/dashboard/src/bundle/Resources/migrations/permissions.yaml --name=2023_10_10_16_14_dashboard_permissions.yaml php bin/console ibexa:migrations:import vendor/ibexa/activity-log/src/bundle/Resources/migrations/dashboard_structure.yaml --name=2023_12_04_13_34_activity_log_dashboard_structure.yaml php bin/console ibexa:migrations:import vendor/ibexa/personalization/src/bundle/Resources/migrations/dashboard_structure.yaml --name=2023_12_05_17_00_personalization_dashboard_structure.yaml php bin/console ibexa:migrations:import vendor/ibexa/product-catalog/src/bundle/Resources/migrations/dashboard_structure.yaml --name=2023_11_20_21_32_product_catalog_dashboard_structure.yaml php bin/console ibexa:migrations:migrate --file=2023_09_23_14_15_dashboard_structure.yaml --file=2023_10_10_16_14_dashboard_permissions.yaml --file=2023_12_04_13_34_activity_log_dashboard_structure.yaml --file=2023_12_05_17_00_personalization_dashboard_structure.yaml --file=2023_11_20_21_32_product_catalog_dashboard_structure.yaml ``` > **Caution: Caution** > > The `2023_10_10_16_14_dashboard_permissions.yaml` migration creates a role dedicated for dashboard management and assigns it to the Editors user group. If you have custom user groups which need to manipulate dashboards, you need to skip this migration, copy it to your migrations folder (by default, `src/Migrations/Ibexa/migrations`) and adjust it according to your needs before execution. For Ibexa Commerce there's an additional migration: ``` php bin/console ibexa:migrations:import vendor/ibexa/order-management/src/bundle/Resources/install/migrations/dashboard_structure.yaml --name=2023_11_20_14_33_order_dashboard_structure.yaml php bin/console ibexa:migrations:migrate --file=2023_11_20_14_33_order_dashboard_structure.yaml ``` ### Ibexa Open Source If you don't have access to Ibexa DXP's `ibexa/installer` package and cannot apply the scripts from `vendor/ibexa/installer` directory, apply the following database update instead: **MySQL** ``` ALTER TABLE `ibexa_token` ADD COLUMN `revoked` BOOLEAN NOT NULL DEFAULT false; ``` **PostgreSQL** ``` ALTER TABLE "ibexa_token" ADD "revoked" BOOLEAN DEFAULT false NOT NULL; ``` ## Revisit configuration ### Revisit mandatory configuration #### Dashboard configuration (Experience) (Commerce) Define "Dashboards" location as contextual tree root: ``` ibexa: system: # ... admin_group: content_tree_module: contextual_tree_root_location_ids: #... - 67 # Dashboards (clean installation) ``` #### User profile Ibexa DXP v4.6 introduced user profile for Backoffice users, allowing users to upload avatars, and provide personal information. This feature is optional, and you can disable it by setting `enabled` flag to `false` in `ibexa.system..user_profile` configuration: ``` # /config/packages/ibexa_admin_ui.yaml ibexa: system: # ... admin_group: user_profile: enabled: false ``` To enable the user profile, you must specify content type identifiers which represent the "editor" user, and field groups to be rendered in the user profile summary: ``` # /config/packages/ibexa_admin_ui.yaml ibexa: system: # ... admin_group: user_profile: enabled: true content_types: ['editor'] field_groups: ['about', 'contact'] ``` You can use your own content type that represents the back office user, or use the default one provided by Ibexa DXP: ``` php bin/console ibexa:migrations:import vendor/ibexa/installer/src/bundle/Resources/install/migrations/2023_12_07_20_23_editor_content_type.yaml --name=2023_12_07_20_23_editor_content_type.yaml php bin/console ibexa:migrations:import vendor/ibexa/installer/src/bundle/Resources/install/migrations/2024_01_09_22_23_editor_permissions.yaml --name=2024_01_09_22_23_editor_permissions.yaml php bin/console ibexa:migrations:migrate --file=2023_12_07_20_23_editor_content_type.yaml --file=2024_01_09_22_23_editor_permissions.yaml ``` #### Site context Site context is used in content tree to display only those content items that belong to the selected website. You can add locations that shouldn't be publicly accessible to the list of excluded paths: ``` # /config/packages/ibexa_site_context.yaml ibexa: system: # ... admin_group: site_context: excluded_paths: - /1/5/ # Users - /1/43/ # Media - /1/55/ # Forms - /1/56/ # Site skeletons - /1/67/ # Dashboards - /1/61/ # Product categorises - /1/65/ # Corporate Account - /1/57/ # Tags ``` ### Revisit optional configuration #### Activity log (Experience) (Commerce) By default, activity log keeps entries for 30 days. You can change this value by setting `ibexa.repositories..activity_log.truncate_after_days` parameter: ``` ibexa: repositories: default: # ... activity_log: truncate_after_days: 10 ``` ### Revisit permissions #### Recent activity (Experience) (Commerce) You must add the `Activity Log / Read` policy (`activity_log/read`) to every role that has access to the back office, at least with the "Only own log" limitation. This policy is mandatory to display the "Recent activity" block in [dashboards](#dashboard-migration), and the "Recent activity" block in [user profiles](#user-profile). The following migration example allows users with the `Editor` role to access their own activity log: ``` - type: role mode: update match: field: identifier value: 'Editor' policies: mode: append list: - module: activity_log function: read limitations: - identifier: activity_log_owner values: [] ``` ## Update Solr configuration Solr configuration changes with the addition of spellchecking feature. Configure the `spellcheck` component in `solrconfig.xml`: ``` default meta_content__text_t solr.DirectSolrSpellChecker internal 0.5 2 1 5 4 0.01 ``` Add this `spellcheck` component to the `/select` request handler: ``` spellcheck ``` > **Note: Note** > > You can [generate new Solr configuration files using `generate-solr-config.sh`](https://doc.ibexa.co/en/latest/search/search_engines/solr_search_engine/install_solr/#generate-solr-configuration-automatically), and merge `spellcheck` configuration by comparing new files with your existing setup. Restart Solr for `solrconfig.xml` changes to take effect. ## Update Elasticsearch schema Elasticsearch schema's templates change, for example, with the addition of new features such as spellchecking. When this happens, you need to erase the index, update the schema, and rebuild the index. To delete an index, you can use the Elasticsearch's REST API. First, use the [`_cat/indices` endpoint](https://www.elastic.co/guide/en/elasticsearch/reference/8.19/cat-indices.html) to list existing indices. For example, the command `curl -H "Accept: application/text" elasticsearch:9200/_cat/indices` returns output like the following: ``` yellow open default_location_eng_gb_54 DoSFV-CtQFylKKVvd48YfA 1 1 1 0 16.7kb 16.7kb yellow open default_location_eng_gb_42 3Z_IrWVHQh2m37jPqQBOcQ 1 1 1 0 20.1kb 20.1kb yellow open default_content_eng_gb_45 y-t4uNQwR4KRJ-N9i3zUog 1 1 1 0 21.3kb 21.3kb yellow open default_content_eng_gb_46 e_LS5qG3RIih6iQRPsNp-w 1 1 1 0 22.5kb 22.5kb yellow open default_content_eng_gb_1 101-1-tQS_2KSvNs2X2JAQ 1 1 17 0 39.8kb 39.8kb yellow open default_location_eng_gb_46 fSGtpljwTpGfascFechmww 1 1 1 0 21kb 21kb (...) ``` Create a list containing all indices used by the DXP, including the [custom indices](https://doc.ibexa.co/en/latest/search/search_engines/elasticsearch/configure_elasticsearch/#define-field-type-mapping-templates) as well. Then, delete them by using the [delete index endpoint](https://www.elastic.co/guide/en/elasticsearch/reference/8.19/indices-delete-index.html) ``` curl --request DELETE 'https://elasticsearch:9200/default_location*' curl --request DELETE 'https://elasticsearch:9200/default_content*' (...) ``` > **Tip: Tip** > > To quickly delete all existing Elasticsearch indices, you can use the `_all` keyword as the name of the index, as in the following request: `curl --request DELETE https://elasticsearch:9200/_all`. Always review the list of existing indices and confirm they are safe to delete before executing this command, as it permanently removes data. To update the schema and then reindex the search, use the following commands: ``` php bin/console ibexa:elasticsearch:put-index-template --overwrite php bin/console ibexa:reindex ``` ## Update to v4.6.latest Now, proceed to the last step, [updating to the latest v4.6 patch version](https://doc.ibexa.co/en/latest/update_and_migration/from_4.6/update_from_4.6/index.md). # Update from v4.6.x to v4.6.latest ## Update the application Note which version you actually have before starting. First, run: **Ibexa Headless** ``` composer require ibexa/headless:4.6.29 --with-all-dependencies --no-scripts composer recipes:install ibexa/headless --force -v ``` **Ibexa Experience** ``` composer require ibexa/experience:4.6.29 --with-all-dependencies --no-scripts composer recipes:install ibexa/experience --force -v ``` **Ibexa Commerce** ``` composer require ibexa/commerce:4.6.29 --with-all-dependencies --no-scripts composer recipes:install ibexa/commerce --force -v ``` Then execute the instructions below starting from the version you're upgrading from. > **Caution: Caution** > > To avoid deprecations when using PHP 8.2, 8.3, or 8.4, run the following commands: > > ``` > composer config extra.runtime.error_handler "\\Ibexa\\Contracts\\Core\\MVC\\Symfony\\ErrorHandler\\Php82HideDeprecationsErrorHandler" > composer dump-autoload > ``` ## v4.6.1 No additional steps needed. ## v4.6.2 #### Database update Run the following scripts: **MySQL** ``` mysql -u -p < vendor/ibexa/installer/upgrade/db/mysql/ibexa-4.6.1-to-4.6.2.sql ``` **PostgreSQL** ``` psql < vendor/ibexa/installer/upgrade/db/postgresql/ibexa-4.6.1-to-4.6.2.sql ``` ## v4.6.3 ### Notification config update The configuration of the package `ibexa/notifications` has changed. This package is required by other packages, such as `ibexa/connector-actito` for [Transactional emails](https://doc.ibexa.co/en/latest/commerce/transactional_emails/transactional_emails/), `ibexa/payment`, or `ibexa/user`. If you are customizing the configuration of the `ibexa/notifications` package, and using SiteAccess aware configuration to change the `Notification` subscriptions, you have to manually change your configuration by using the new node name `notifier` instead of the old `notifications`. For example, the following v4.6.2 config: ``` ibexa: system: my_siteacces_name: notifications: # old subscriptions: Ibexa\Contracts\Shipping\Notification\ShipmentStatusChange: channels: - sms ``` becomes the following from v4.6.3: ``` ibexa: system: my_siteacces_name: notifier: # new subscriptions: Ibexa\Contracts\Shipping\Notification\ShipmentStatusChange: channels: - sms ``` ## v4.6.4 #### Database update Run the following scripts: **MySQL** ``` mysql -u -p < vendor/ibexa/installer/upgrade/db/mysql/ibexa-4.6.3-to-4.6.4.sql ``` **PostgreSQL** ``` psql < vendor/ibexa/installer/upgrade/db/postgresql/ibexa-4.6.3-to-4.6.4.sql ``` ## v4.6.5 No additional steps needed. ## v4.6.6 No additional steps needed. ## v4.6.7 No additional steps needed. ## v4.6.8 No additional steps needed. ## v4.6.9 No additional steps needed. ## v4.6.10 No additional steps needed. ## v4.6.11 ### Ibexa Cloud Update Platform.sh configuration for PHP and Varnish. Generate new configuration with the following command: ``` composer ibexa:setup --platformsh ``` Review the changes applied to `.platform.app.yaml` and `.platform/`, merge with your custom settings if needed, and commit them to Git. ## v4.6.12 If the new bundle `ibexa/core-search` has not been added by the recipes, enable it by adding the following line in `config/bundles.php`: ``` Ibexa\Bundle\CoreSearch\IbexaCoreSearchBundle::class => ['all' => true], ``` ## v4.6.13 This release comes with a command to clean up duplicated entries in the `ezcontentobject_attribute` table, which were created due to an issue related to previewing content in different languages. If you're affected, remove the duplicated entries by running the following command: ``` php bin/console ibexa:content:remove-duplicate-fields ``` > **Caution: Caution** > > Remember about [**proper database backup**](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/backup/index.md) before running the command in the production environment. You can customize the behavior of the command with the following options: - `--batch-size` or `-b` - number of attributes affected per iteration. Default value = 10000. - `--max-iterations` or `-i` - maximum iterations count. Default value = -1 (unlimited). - `--sleep` or `-s` - wait time between iterations, in milliseconds. Default value = 0. ## v4.6.14 ### Security This release contains security fixes. For more information, see [the published security advisory](https://developers.ibexa.co/security-advisories/ibexa-sa-2024-006-vulnerabilities-in-content-name-pattern-commerce-shop-and-varnish-vhost-templates). For each of the following fixes, evaluate the vulnerability to determine whether you might have been affected. If so, take appropriate action, for example by [revoking passwords](https://doc.ibexa.co/en/latest/users/passwords/#revoking-passwords) for all affected users. #### BREACH vulnerability The [BREACH](https://www.breachattack.com/) attack is a security vulnerability against HTTPS when using HTTP compression. If you're using Varnish, update the VCL configuration to stop compressing both the Ibexa DXP's REST API and JSON responses from your backend. Fastly users are not affected. **Varnish on Ibexa Cloud** Update Platform.sh configuration and scripts. Generate new configuration with the following command: ``` composer ibexa:setup --platformsh ``` Review the changes, merge with your custom settings if needed, and commit them to Git before deployment. **Varnish 6** Update your Varnish VCL file to align it with the [`vendor/ibexa/http-cache/docs/varnish/vcl/varnish6.vcl`](https://github.com/ibexa/http-cache/blob/4.6/docs/varnish/vcl/varnish6.vcl) file. **Varnish 7** Update your Varnish VCL file to align it with the [`vendor/ibexa/http-cache/docs/varnish/vcl/varnish7.vcl`](https://github.com/ibexa/http-cache//blob/4.6/docs/varnish/vcl/varnish7.vcl) file. ``` If you're not using a reverse proxy like Varnish or Fastly, adjust the compressed `Content-Type` in the web server configuration. For more information, see the [updated Apache and nginx template configuration](https://github.com/ibexa/post-install/pull/86/files). #### XSS in Content name pattern There are no additional update steps to execute. #### Outdated version of jQuery in ibexa/commerce-shop package Only users of the [old Commerce solution](https://doc.ibexa.co/en/latest/update_and_migration/from_4.3/update_from_4.3_old_commerce/index.md) are affected. There are no additional update steps to execute. ### Other changes #### Disable translations of identifiers in Product Catalog's categories The possibility of translating identifiers and parent information for the Categories in Product Catalog might lead to data consistency issues. Disable it by running the following migration: ``` php bin/console ibexa:migrations:import vendor/ibexa/product-catalog/src/bundle/Resources/migrations/2024_07_25_07_00_non_translatable_product_categories.yaml --name=2024_07_25_07_00_non_translatable_product_categories.yaml php bin/console ibexa:migrations:migrate --file=2024_07_25_07_00_non_translatable_product_categories.yaml ``` #### Update web server configuration Adjust the web server configuration to prevent direct access to the `index.php` file when using URLs consisting of multiple path segments. See [the updated Apache and nginx template files](https://github.com/ibexa/post-install/pull/70/files) for more information. ## v4.6.15 ### Removed `symfony/orm-pack` and `symfony/serializer-pack` dependencies This release no longer directly requires the `symfony/orm-pack` and `symfony/serializer-pack` Composer dependencies, which can remove some dependencies from your project during the update process. If you rely on them in your project, for example by using Symfony's `ObjectNormalizer` to create your own REST endpoints, run the following command before updating Ibexa packages: ``` composer require symfony/serializer-pack symfony/orm-pack ``` Then, verify that Symfony Flex installed the versions you were using before. ## v4.6.16 No additional steps needed. ## v4.6.17 ### Security This release contains security fixes. For more information, see [the published security advisory](https://developers.ibexa.co/security-advisories/ibexa-sa-2025-001-vulnerabilities-in-shopping-cart-and-publish-unscheduling). For each of the following fixes, evaluate the vulnerability to determine whether you might have been affected. If so, take appropriate action. #### CartOwner permission limitation exposes carts This release fixes a critical vulnerability in the REST API regarding shopping carts. There are no additional update steps to execute. #### Unauthorized user can cancel scheduled publish events This release fixes vulnerability in publish scheduling, ensures that `edit/create` policies are correctly checked. There are no additional update steps to execute. #### Dependency upgrades This release upgrades the requirements for [Twig to v3.19](https://github.com/twigphp/Twig/security/advisories/GHSA-3xg3-cgvq-2xwr) and [PHPSpreadsheet to v1.29.9](https://github.com/PHPOffice/PhpSpreadsheet/security), resolving several vulnerabilities of varying severity in those dependencies. There are no additional update steps to execute. ## v4.6.18 No additional steps needed. ## v4.6.19 ### Security This release fixes a critical vulnerability in the [RichText field type](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/richtextfield/index.md). By entering a maliciously crafted input into the RichText field type's XML, the attacker could perform an attack using [XML external entity (XXE) injection](https://portswigger.net/web-security/xxe). To exploit this vulnerability, an attacker would need to have edit permission to content with RichText fields. For more information, see the [published security advisory IBEXA-SA-2025-002](https://developers.ibexa.co/security-advisories/ibexa-sa-2025-002-xxe-vulnerability-in-richtext). Evaluate the vulnerability to determine whether you might have been affected. If so, take appropriate action. There are no additional update steps to execute. ### Ibexa Rector The new [Ibexa Rector](https://github.com/ibexa/rector/) package is now available. It's an optional package based on [Rector](https://getrector.com/) and comes with additional rules for working with Ibexa code. You can use it to get rid of PHP code deprecations and start preparing your project for the next major release. > **Note: Note** > > Ibexa Rector requires PHP 8.3 and you must upgrade your codebase first. > To do it, you can use Rector and the [existing PHP upgrade sets](https://getrector.com/documentation/integration-to-new-project#content-2-upgrade-php-first). To get started with Ibexa Rector, execute the following steps: 1. Add the Composer dependency: ``` composer require --dev ibexa/rector:^4.6 ``` 2. Adjust the created `rector.php` configuration file to match your project structure 3. Run Rector in the dry-run mode to preview the changes: ``` vendor/bin/rector --dry-run ``` 4. Run Rector: ``` vendor/bin/rector ``` ## v4.6.20 No additional steps needed. ## v4.6.21 ### Security This security advisory resolves XSS vulnerabilities in several parts of the back office of the DXP. Back office access and varying levels of editing and management permissions are required to exploit these vulnerabilities. For more information, see the [security advisory IBEXA-SA-2025-003](https://developers.ibexa.co/security-advisories/ibexa-sa-2025-003-xss-vulnerabilities-in-back-office). Evaluate the vulnerability to determine whether you might have been affected. If so, take appropriate action. There are no additional update steps to execute. ### Database update Run the following scripts: **MySQL** ``` mysql -u -p \ < vendor/ibexa/installer/upgrade/db/mysql/ibexa-4.6.20-to-4.6.21.sql ``` **PostgreSQL** ``` psql \ < vendor/ibexa/installer/upgrade/db/postgresql/ibexa-4.6.20-to-4.6.21.sql ``` ## v4.6.22 ### Added support for Solr 9 This release adds support for [Solr 9](https://doc.ibexa.co/en/latest/getting_started/requirements/#search). To update Solr within an existing Ibexa DXP project, first refer to the [Solr 9 upgrade planning](https://solr.apache.org/guide/solr/latest/upgrade-notes/major-changes-in-solr-9.html) instructions. Then, follow the [instructions for setting up Solr 9 with Ibexa DXP](https://doc.ibexa.co/en/latest/search/search_engines/solr_search_engine/install_solr/#configure-and-start-solr) and merge them with your custom configuration. Changes include: 1. Configuration files - the `schema.xml` configuration file became [`managed-schema.xml`](https://solr.apache.org/guide/solr/latest/upgrade-notes/major-changes-in-solr-6.html#managed-schema-is-now-the-default) - the [removed `LatLonType` field is replaced by the `LatLonPointSpatialField` field](https://solr.apache.org/guide/solr/latest/upgrade-notes/major-changes-in-solr-7.html#deprecations-and-removed-features) 2. New [Solr version parameter](https://doc.ibexa.co/en/latest/search/search_engines/solr_search_engine/install_solr/#configure-solr-version) Once Solr 9 is fully configured, [refresh the search index](https://doc.ibexa.co/en/latest/search/reindex_search/index.md). ### Set character set for activity log tables (Experience) (Commerce) When using MySQL or MariaDB, run the following script to ensure correct character set for activity log tables: **MySQL** ``` mysql -u -p \ < vendor/ibexa/installer/upgrade/db/mysql/ibexa-4.6.21-to-4.6.22.sql ``` ## v4.6.23 No additional steps needed. ## v4.6.24 ### Database update **MySQL** ``` mysql -u -p \ < vendor/ibexa/installer/upgrade/db/mysql/ibexa-4.6.23-to-4.6.24.sql ``` **PostgreSQL** ``` psql \ < vendor/ibexa/installer/upgrade/db/postgresql/ibexa-4.6.23-to-4.6.24.sql ``` ## v4.6.25 ### Form Builder performance fix: missing indexes on form submission data (Experience) (Commerce) In large production databases, the `ezform_form_submissions` and `ezform_form_submission_data` tables may contain a lot of rows. Missing indexes can cause high CPU load and slow queries. Run the provided SQL upgrade script to add the missing indexes to your database: **MySQL** ``` mysql -u -p \ < vendor/ibexa/installer/upgrade/db/mysql/ibexa-4.6.24-to-4.6.25.sql ``` **PostgreSQL** ``` psql \ < vendor/ibexa/installer/upgrade/db/postgresql/ibexa-4.6.24-to-4.6.25.sql ``` ## v4.6.26 No additional steps needed. ## v4.6.27 ### Elasticsearch 8 support As of v4.6.27, Ibexa DXP adds optional support for Elasticsearch 8.19 or higher through the new `ibexa/elasticsearch8` package. By default, Ibexa DXP continues to support Elasticsearch 7.16.2+ with the `ibexa/elasticsearch` package. To use Elasticsearch 8, follow these steps: #### Install Elasticsearch 8 package Replace the existing Elasticsearch package and install Elasticsearch 8: ``` composer require ibexa/elasticsearch8:4.6.29 ``` #### Update Elasticsearch server Upgrade your Elasticsearch server to version 8.19 or higher. For detailed instructions, follow the [Elasticsearch upgrade guide](https://www.elastic.co/guide/en/elastic-stack/8.19/upgrading-elastic-stack.html#prepare-to-upgrade). When you use Ibexa Cloud, see [Elasticsearch service](https://docs.upsun.com/add-services/elasticsearch.html) for a list of supported versions. #### Update configuration Update your configuration in `config/packages/ibexa_elasticsearch.yaml` as decribed below: ##### Replace connection pool settings The `connection_pool` and `connection_selector` settings are ignored when using Elasticsearch 8. Replace them with appropriate `node_pool_selector` and `node_pool_resurrect` settings: ``` # Elasticsearch 7 configuration ibexa_elasticsearch: connections: default: connection_pool: 'Elasticsearch\\ConnectionPool\\StaticNoPingConnectionPool' connection_selector: 'Elasticsearch\\ConnectionPool\\Selectors\\RoundRobinSelector' ``` ``` # Elasticsearch 8 configuration ibexa_elasticsearch: connections: default: node_pool_selector: 'Elastic\\Transport\\NodePool\\Selector\\RoundRobin' node_pool_resurrect: 'Elastic\\Transport\\NodePool\\Resurrect\\NoResurrect' ``` For more information, see [Connection pool and node pool settings](https://doc.ibexa.co/en/4.6/search/search_engines/elasticsearch/configure_elasticsearch/#connection-pool-and-node-pool-settings). ##### Remove trace option The `trace` debugging option is no longer available in Elasticsearch 8: ``` # Elasticsearch 7 configuration ibexa_elasticsearch: connections: default: debug: true trace: true ``` ``` # Elasticsearch 8 configuration ibexa_elasticsearch: connections: default: debug: true # Trace option is no longer available ``` #### Reindex content After upgrading to Elasticsearch 8 and updating your configuration, reindex the search engine: 1. Push the index templates: ``` php bin/console ibexa:elasticsearch:put-index-template --overwrite ``` 2. Reindex your content: ``` php bin/console ibexa:reindex ``` ### Removed Composer dependencies The following unused Composer dependencies have been removed from `ibexa/core`: - `guzzlehttp/guzzle` - `php-http/guzzle6-adapter` If your project uses Guzzle directly, you should add these dependencies to your project's `composer.json` file. To check if you need to add these dependencies, run: ``` composer why guzzlehttp/guzzle composer why php-http/guzzle6-adapter ``` If only the `ibexa/core` entry appears in the output, check your codebase to determine if you use Guzzle directly. If you do, add the required dependencies to your project: ``` composer require guzzlehttp/guzzle:^6.5 php-http/guzzle6-adapter:^2.0 ``` ### Messenger support in CDP If you're using [CDP](https://doc.ibexa.co/en/latest/cdp/cdp/index.md) and haven't configured Ibexa Messenger yet, do so now. Follow the [Messenger setup instructions](https://doc.ibexa.co/en/4.6/infrastructure_and_maintenance/background_tasks/#install-package) to continue. ## Notify support Inform the support team that you have updated your installation. They update your Service portal to match the new version. This ensures that you receive notifications about new maintenance releases and security advisories for the correct version. You can contact the support team at [support@ibexa.co](mailto:support@ibexa.co) or through your [Service portal](https://support.ibexa.co). With the product updated to the latest version, you can now finish the update process or proceed to updating the LTS Updates packages. ## v4.6.28 ### Database update (Experience) (Commerce) Run the provided SQL upgrade script to adapt your database to latest change in [form builder](https://doc.ibexa.co/en/latest/content_management/forms/form_builder_guide/index.md)'s `max_length` validator behavior: **MySQL** ``` mysql -u -p \ < vendor/ibexa/installer/upgrade/db/mysql/ibexa-4.6.27-to-4.6.28.sql ``` **PostgreSQL** ``` psql \ < vendor/ibexa/installer/upgrade/db/postgresql/ibexa-4.6.27-to-4.6.28.sql ``` Prior, `0` was interpreted as "no length limit". Now, `0` is interpreted as "length limited to zero characters" and `NULL` as "no length limit". ## v4.6.29 ### GraphQL package update Due to the [GHSA-68jq-c3rv-pcrr security issue](https://github.com/advisories/GHSA-68jq-c3rv-pcrr), the GraphQL package requirements have been updated to allow installing higher versions in which this issue is resolved. When doing the update, you have two options: #### Update GraphQL packages and custom code (recommended) Make sure the `webonyx/graphql-php` package is installed in a version higher or equal to v15.31.5. If you [extended GraphQL to support custom field types](https://doc.ibexa.co/en/latest/api/graphql/graphql_custom_ft/index.md), update the returned expression from `@=resolver(...)` to `@=query(...)` and change the argument syntax from an array to variadic arguments as in the following example: ``` -return sprintf('@=resolver("MyFieldValue", [field, %s])', $myArg); +return sprintf('@=query("MyFieldValue", field, %s)', $myArg); ``` Then, regenerate the GraphQL schema by running: ``` rm -rf config/graphql/types/ibexa/ php bin/console ibexa:graphql:generate-schema ``` #### Implement other countermeasures If updating the GraphQL packages isn't possible right now, for example because the project is using PHP 7.4 where the fix is not available, review the security issue carefully and assess the danger. If you choose to implement countermeasures without updating the GraphQL packages, for example by restricting access to the GrapQL endpoint with rate limiting, authentication, or [WAF](https://en.wikipedia.org/wiki/Web_application_firewall), then you can silence the advisory in `composer.json`: ``` "config": { "audit": { "ignore": { "GHSA-68jq-c3rv-pcrr": "Description of the countermeasures you've implemented causing this one to be safe to ignore." } } } ``` In addition, consider upgrading your project to one of [the actively supported PHP versions](https://doc.ibexa.co/en/latest/getting_started/requirements/#php). ### Database update (Experience) (Commerce) Run the provided SQL upgrade script to update your database: **MySQL** ``` mysql -u -p \ < vendor/ibexa/installer/upgrade/db/mysql/ibexa-4.6.28-to-4.6.29.sql ``` **PostgreSQL** ``` psql \ < vendor/ibexa/installer/upgrade/db/postgresql/ibexa-4.6.28-to-4.6.29.sql ``` ## LTS Updates [LTS Updates](https://doc.ibexa.co/en/4.6/ibexa_products/editions/#lts-updates) are standalone packages with their own update procedures. To use the [latest features](https://doc.ibexa.co/en/latest/release_notes/ibexa_dxp_v4.6/index.md) added to them, update them separately with the following commands: **Discounts** ### Discounts (LTS Update) (Commerce) To install the [Discounts feature](https://doc.ibexa.co/en/latest/discounts/discounts_guide/index.md), see the [installation instructions](https://doc.ibexa.co/en/4.6/discounts/install_discounts/). If you're already using it, run the following command to get the latest version of this feature: ``` composer require ibexa/discounts:4.6.29 ibexa/discounts-codes:4.6.29 ``` Then apply manually the changes described below. ### Discounts v4.6.20 #### Policy changes The `discount/view` policy is no longer required for the store customers to use a discount and must be removed from all users who are not managing discounts. The policy allows to access all the discount details, including the coupon codes to activate them, which could lead to system abuse. To learn more, see the [discounts policies overview](https://doc.ibexa.co/en/4.6/permissions/policies/). #### Database update Run the following scripts: **MySQL** ``` CREATE TABLE ibexa_discount_code_usage ( id INT AUTO_INCREMENT NOT NULL, discount_code_id INT NOT NULL, order_id INT NOT NULL, discriminator VARCHAR(10) NOT NULL, used_at DATETIME NOT NULL COMMENT '(DC2Type:datetime_immutable)', INDEX ibexa_discount_code_usage_discount_code_idx (discount_code_id), INDEX ibexa_discount_code_usage_order_idx (order_id), PRIMARY KEY(id) ) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_520_ci` ENGINE = InnoDB; CREATE TABLE ibexa_discount_code_usage_email ( id INT NOT NULL, user_email VARCHAR(190) DEFAULT NULL, INDEX ibexa_discount_code_usage_email_idx (user_email), UNIQUE INDEX ibexa_discount_codes_usage_email_uidx (id, user_email), PRIMARY KEY(id) ) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_520_ci` ENGINE = InnoDB; CREATE TABLE ibexa_discount_code_usage_user ( id INT NOT NULL, user_id INT DEFAULT NULL, INDEX ibexa_discount_code_usage_user_idx (user_id), UNIQUE INDEX ibexa_discount_codes_usage_user_uidx (id, user_id), PRIMARY KEY(id) ) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_520_ci` ENGINE = InnoDB; ALTER TABLE ibexa_discount_code_usage ADD CONSTRAINT ibexa_discount_code_usage_code_fk FOREIGN KEY (discount_code_id) REFERENCES ibexa_discount_code (id) ON UPDATE CASCADE ON DELETE CASCADE; ALTER TABLE ibexa_discount_code_usage ADD CONSTRAINT ibexa_discount_code_usage_order_fk FOREIGN KEY (order_id) REFERENCES ibexa_order (id) ON UPDATE CASCADE ON DELETE CASCADE; ALTER TABLE ibexa_discount_code_usage_email ADD CONSTRAINT ibexa_discount_code_usage_email_fk FOREIGN KEY (id) REFERENCES ibexa_discount_code_usage (id) ON UPDATE CASCADE ON DELETE CASCADE; ALTER TABLE ibexa_discount_code_usage_user ADD CONSTRAINT ibexa_discount_code_usage_user_fk FOREIGN KEY (id) REFERENCES ibexa_discount_code_usage (id) ON UPDATE CASCADE ON DELETE CASCADE; ALTER TABLE ibexa_discount_code_usage_user ADD CONSTRAINT ibexa_discount_code_usage_user_content_fk FOREIGN KEY (user_id) REFERENCES ezuser (contentobject_id) ON UPDATE CASCADE ON DELETE CASCADE; ``` **PostgreSQL** ``` CREATE TABLE ibexa_discount_code_usage ( id SERIAL NOT NULL, discount_code_id INT NOT NULL, order_id INT NOT NULL, discriminator VARCHAR(10) NOT NULL, used_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id) ); CREATE INDEX ibexa_discount_code_usage_discount_code_idx ON ibexa_discount_code_usage (discount_code_id); CREATE INDEX ibexa_discount_code_usage_order_idx ON ibexa_discount_code_usage (order_id); COMMENT ON COLUMN ibexa_discount_code_usage.used_at IS '(DC2Type:datetime_immutable)'; CREATE TABLE ibexa_discount_code_usage_email ( id INT NOT NULL, user_email VARCHAR(190) DEFAULT NULL, PRIMARY KEY(id) ); CREATE INDEX ibexa_discount_code_usage_email_idx ON ibexa_discount_code_usage_email (user_email); CREATE UNIQUE INDEX ibexa_discount_codes_usage_email_uidx ON ibexa_discount_code_usage_email (id, user_email); CREATE TABLE ibexa_discount_code_usage_user ( id INT NOT NULL, user_id INT DEFAULT NULL, PRIMARY KEY(id) ); CREATE INDEX ibexa_discount_code_usage_user_idx ON ibexa_discount_code_usage_user (user_id); CREATE UNIQUE INDEX ibexa_discount_codes_usage_user_uidx ON ibexa_discount_code_usage_user (id, user_id); ALTER TABLE ibexa_discount_code_usage ADD CONSTRAINT ibexa_discount_code_usage_code_fk FOREIGN KEY (discount_code_id) REFERENCES ibexa_discount_code (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE; ALTER TABLE ibexa_discount_code_usage ADD CONSTRAINT ibexa_discount_code_usage_order_fk FOREIGN KEY (order_id) REFERENCES ibexa_order (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE; ALTER TABLE ibexa_discount_code_usage_email ADD CONSTRAINT ibexa_discount_code_usage_email_fk FOREIGN KEY (id) REFERENCES ibexa_discount_code_usage (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE; ALTER TABLE ibexa_discount_code_usage_user ADD CONSTRAINT ibexa_discount_code_usage_user_fk FOREIGN KEY (id) REFERENCES ibexa_discount_code_usage (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE; ALTER TABLE ibexa_discount_code_usage_user ADD CONSTRAINT ibexa_discount_code_usage_user_content_fk FOREIGN KEY (user_id) REFERENCES ezuser (contentobject_id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE; ``` ### Discounts v4.6.22 #### Database update Run the following scripts: **MySQL** ``` ALTER TABLE ibexa_discount ADD override_prioritization tinyint(1) NOT NULL DEFAULT 0; CREATE INDEX ibexa_discount_prioritization_idx ON ibexa_discount (override_prioritization, type, priority); ALTER TABLE ibexa_discount_code ADD global_limit INT DEFAULT NULL; ``` **PostgreSQL** ``` ALTER TABLE ibexa_discount ADD override_prioritization tinyint(1) NOT NULL DEFAULT 0; CREATE INDEX ibexa_discount_prioritization_idx ON ibexa_discount (override_prioritization, type, priority); ALTER TABLE ibexa_discount_code ADD global_limit INT DEFAULT NULL; ``` ### Discounts v4.6.24 #### Database update Run the following scripts: **MySQL** ``` ALTER TABLE ibexa_discount ADD indexed_at DATETIME DEFAULT NULL COMMENT '(DC2Type:datetime_immutable)'; CREATE INDEX ibexa_discount_indexed_at_idx ON ibexa_discount (indexed_at); ``` **PostgreSQL** ``` ALTER TABLE ibexa_discount ADD indexed_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL; COMMENT ON COLUMN ibexa_discount.indexed_at IS '(DC2Type:datetime_immutable)'; CREATE INDEX ibexa_discount_indexed_at_idx ON ibexa_discount (indexed_at); ``` **AI Actions** ### AI Actions (LTS Update) To install the [AI actions feature](https://doc.ibexa.co/en/latest/ai_actions/ai_actions_guide/index.md), see the [installation instructions](https://doc.ibexa.co/en/4.6/ai_actions/install_ai_actions/). If you're already using it, run the following command to get the latest version of this feature: ``` composer require ibexa/connector-ai:4.6.29 ibexa/connector-openai:4.6.29 ``` **Date and time attribute** ### Date and time attribute (LTS Update) To install the [Date and time attribute](https://doc.ibexa.co/en/latest/product_catalog/attributes/date_and_time/index.md), see the [installation instructions](https://doc.ibexa.co/en/4.6/pim/attributes/date_and_time/#installation). If you're already using it, run the following command to get the latest version of this feature: ``` composer require ibexa/product-catalog-date-time-attribute:4.6.29 ``` **Symbol attribute** ### Symbol attribute (LTS Update) To install the [Symbol attribute](https://doc.ibexa.co/en/latest/product_catalog/attributes/symbol_attribute_type/index.md), see the [installation instructions](https://doc.ibexa.co/en/4.6/pim/attributes/symbol_attribute_type/#installation). If you're already using it, run the following command to get the latest version of this feature: ``` composer require ibexa/product-catalog-symbol-attribute:4.6.29 ``` **Integrated help** ### Integrated help (LTS Update) See [Integrated help](https://doc.ibexa.co/en/latest/administration/back_office/integrated_help/index.md) for more information. If you're already using it, run the following command to get the latest version of this feature: ``` composer require ibexa/integrated-help:4.6.29 ``` **Collaborative editing** ### Collaborative editing (LTS Update) To learn more about the [Collaborative editing](https://doc.ibexa.co/en/latest/content_management/collaborative_editing/collaborative_editing_guide/), see the [installation instructions](https://doc.ibexa.co/en/4.6/content_management/collaborative_editing/install_collaborative_editing). If you're already using it, run the following command to get the latest version of this feature: ``` composer require ibexa/share:4.6.29 ibexa/collaboration:4.6.29 ``` If you're using the Real-time collaborative editing, in addition run: ``` composer require ibexa/fieldtype-richtext-rte:4.6.29 ibexa/ckeditor-premium:4.6.29 ``` ``` # Update from v4.6 to v5.0 ## Update from v4.6.x to v4.6.latest Before you update to v5.0, you need to [update to the latest maintenance release of v4.6 (v4.6.29)](https://doc.ibexa.co/en/latest/update_and_migration/from_4.6/update_from_4.6/index.md). ### Move from old to new Commerce If you've chosen to use the [deprecated Commerce packages](https://doc.ibexa.co/en/latest/update_and_migration/from_4.3/update_from_4.3_old_commerce/index.md) during the update to 4.4, you have to move to [new Commerce ones](https://doc.ibexa.co/en/latest/update_and_migration/from_4.3/update_from_4.3_new_commerce/index.md). ## Update from v4.6.latest to v5.0.0 When you have the last version of 4.6, you can update to v5.0.0. ### Requirements First, match v5.0's [requirements](https://doc.ibexa.co/en/latest/getting_started/requirements/index.md). It supports only PHP 8.3 and above. ### Update custom code for PHP 8.3+ and DXP 4.6 It's important to stop using deprecated PHP classes as they're removed in 5.0. The [`ibexa/compatibility-layer`](https://doc.ibexa.co/en/latest/update_and_migration/from_3.3/to_4.0/#add-compatibility-layer-package) isn't supported in 5.0. If you use it, remove it (`composer remove ibexa/compatibility-layer`) and make the necessary changes. See [Ibexa DXP v4.0 deprecations and backwards compatibility breaks](https://doc.ibexa.co/en/latest/release_notes/ibexa_dxp_v4.0_deprecations/index.md) for the list of changes. [Rector](https://getrector.com/) and the Ibexa rule sets help to upgrade your code. Install [`ibexa/rector`](https://github.com/ibexa/rector) which contains rules to ensure custom code is up to date with DXP 4.6: ``` composer require --dev ibexa/rector ``` Customize the `rector.php` config file by: - making it match your directory structure (for example, you may not have the `tests` directory) - adding project-specific rules: - specify [PHP rules by using `withPhpSets`](https://getrector.com/documentation/set-lists#content-php-sets) - specify [Symfony, Twig, or Doctrine rules by using `withComposerBased`](https://getrector.com/documentation/composer-based-sets). It's recommended to activate one rule set at a time and preview the output by running Rector with the `--dry-run` option to decide which rulesets should be used and in which order. Your configuration could look like the following example: ``` return RectorConfig::configure() ->withPaths( [ __DIR__ . '/src', ] ) ->withSets( [ IbexaSetList::IBEXA_46->value, ] ) ->withPhpSets(php83: true) ->withComposerBased(symfony: true) ; ``` Run the following command to preview the changes done by Rector: ``` php vendor/bin/rector --dry-run ``` ### Move from annotation to attribute Delete [`config/routes/annotations.yaml`](https://github.com/symfony/recipes/blob/main/doctrine/annotations/1.0/config/routes/annotations.yaml) if you haven't customized it. If you have customized it, change all occurrences of `type: annotation` to `type: attribute`. The `config/routes.yaml` file should start with the following declaration from the [Symfony recipe](https://github.com/symfony/recipes/blob/main/symfony/routing/7.0/config/routes.yaml): ``` controllers: resource: path: ../src/Controller/ namespace: App\Controller type: attribute ``` You can add the new declaration to the top of the file manually, or recreate the file by running `composer sync-recipes symfony/routing --force --reset`. ### Remove GraphQL schema The GraphQL schema used in 4.6 isn't compatible with version 5.0 and must be deleted. You can do it, for example, with the following command: ``` rm -r config/graphql ``` ### Update Ibexa DXP application #### Update package requirements Ibexa DXP 5.0 is based on Symfony 7.3 and both must be updated. Your development packages must be updated as well. The example below assumes that [`symfony/debug-pack`](https://symfony.com/packages/Debug%20Pack) and `ibexa/rector` are installed. Adjust the list based on your project requirements. Notice the use of the `--no-update` option to only edit the `composer.json` entries and avoid triggering the package update and Composer scripts. **Ibexa Headless** ``` # Update required PHP version composer require --no-update 'php:>=8.3'; # Update required Symfony version composer config extra.symfony.require '7.3.*' # Upgrade Ibexa and Symfony packages: application composer require --no-update \ ibexa/headless:5.0.7 \ symfony/console:^7.3 \ symfony/dotenv:^7.3 \ symfony/framework-bundle:^7.3 \ symfony/runtime:^7.3 \ symfony/yaml:^7.3 \ ; # Upgrade Ibexa and Symfony packages: development tools composer require --dev --no-update \ ibexa/rector:5.0.7 \ symfony/debug-bundle:^7.3 \ symfony/stopwatch:^7.3 \ symfony/web-profiler-bundle:^7.3 \ ; ``` **Ibexa Experience** ``` # Update required PHP version composer require --no-update 'php:>=8.3'; # Update required Symfony version composer config extra.symfony.require '7.3.*' # Upgrade Ibexa and Symfony packages: application composer require --no-update \ ibexa/experience:5.0.7 \ symfony/console:^7.3 \ symfony/dotenv:^7.3 \ symfony/framework-bundle:^7.3 \ symfony/runtime:^7.3 \ symfony/yaml:^7.3 \ ; # Upgrade Ibexa and Symfony packages: development tools composer require --dev --no-update \ ibexa/rector:5.0.7 \ symfony/debug-bundle:^7.3 \ symfony/stopwatch:^7.3 \ symfony/web-profiler-bundle:^7.3 \ ; ``` **Ibexa Commerce** ``` # Update required PHP version composer require --no-update 'php:>=8.3'; # Update required Symfony version composer config extra.symfony.require '7.3.*' # Upgrade Ibexa and Symfony packages: application composer require --no-update \ ibexa/commerce:5.0.7 \ symfony/console:^7.3 \ symfony/dotenv:^7.3 \ symfony/framework-bundle:^7.3 \ symfony/runtime:^7.3 \ symfony/yaml:^7.3 \ ; # Upgrade Ibexa and Symfony packages: development tools composer require --dev --no-update \ ibexa/rector:5.0.7 \ symfony/debug-bundle:^7.3 \ symfony/stopwatch:^7.3 \ symfony/web-profiler-bundle:^7.3 \ ; ``` #### Remove 4.6 LTS Updates constraints 4.6 LTS Update packages are included by default in 5.0. Remove them from your composer.json to avoid updating their version manually with each update. For example, the following command removes several released LTS Updates for 4.6 from `composer.json`: ``` composer remove --no-update \ ibexa/connector-ai \ ibexa/connector-openai \ ibexa/product-catalog-date-time-attribute \ ibexa/product-catalog-symbol-attribute \ ibexa/discounts \ ibexa/discounts-codes \ ibexa/collaboration \ ibexa/share \ ; ``` #### Remove separate Elasticsearch 8 package If you were using the separate `ibexa/elasticsearch8` package in v4.6, you should switch back to the built-in `ibexa/elasticsearch` package, as it now supports both Elasticsearch 7 and Elasticsearch 8. ``` composer remove --no-update ibexa/elasticsearch8 ``` The `ibexa/elasticsearch` package is automatically installed as part of your Ibexa DXP 5.0 update. Your existing Elasticsearch 8 server and configuration continue to work with the `ibexa/elasticsearch` package. #### Remove PHP 8.2 error handler If you were using the [`Php82HideDeprecationsErrorHandler`](https://doc.ibexa.co/en/latest/update_and_migration/from_4.6/update_from_4.6/#v468) to avoid deprecation messages, you must remove it: ``` composer config --unset extra.runtime.error_handler ``` #### Update required packages It's time to apply the new composer.json and update the dependencies: ``` composer update --with-all-dependencies --no-scripts ``` #### Remove Stimulus bootstrap To help moving from Symfony's Webpack Encore bundle 1.x to 2.x, delete the Stimulus bootstrap file and reset Webpack Encore recipe: ``` rm assets/bootstrap.js composer recipes:install symfony/webpack-encore-bundle --reset --force --yes ``` Compare with your previous version, merge them together and test your customizations if needed. #### Apply Ibexa DXP recipe **Ibexa Headless** ``` composer recipes:install ibexa/headless --reset --force --yes ``` **Ibexa Experience** ``` composer recipes:install ibexa/experience --reset --force --yes ``` **Ibexa Commerce** ``` composer recipes:install ibexa/commerce --reset --force --yes ``` #### Sort commands Executing the recipes appends a new command at the end`composer.json`'s `auto-scripts` section, resulting in incorrect script order. You have to manually sort the commands so the `tsconfig.json` file is created by `yarn ibexa-generate-tsconfig` before being used by `ibexa:encore:compile`. Your `auto-scripts` entry should look like this: ``` "auto-scripts": { "cache:clear": "symfony-cmd", "assets:install %PUBLIC_DIR%": "symfony-cmd", "yarn install": "script", "yarn ibexa-generate-tsconfig --use-relative-paths": "script", "ibexa:encore:compile --config-name app": "symfony-cmd", "bazinga:js-translation:dump %PUBLIC_DIR%/assets --merge-domains": "symfony-cmd", "ibexa:encore:compile": "symfony-cmd", "ibexa:encore:compile --frontend-configs-name ibexa,internals,libs,richtext": "symfony-cmd" }, ``` #### Remove Ibexa Icons Remove from your `config/bundles.php` the line about `IbexaIconsBundle`. #### Post update script ``` rm -rf var/cache composer run-script post-update-cmd ``` ### Update database > **Caution: Caution** > > Always back up your data before running any database update scripts. > > After updating the database, clear the cache. > > Don't use `--force` argument for `mysql` / `psql` commands when performing update queries. If there is any problem during the update, it's best if the query fails immediately, so you can fix the underlying problem before you execute the update again. If you leave this for later you risk ending up with an incompatible database, though the problems might not surface immediately. The main schema has changed and the provided SQL file `ibexa-4.6.latest-to-5.0.0.sql` updates it: **MySQL** ``` mysql -u -p < vendor/ibexa/installer/upgrade/db/mysql/ibexa-4.6.latest-to-5.0.0.sql ``` **PostgreSQL** ``` psql < vendor/ibexa/installer/upgrade/db/postgresql/ibexa-4.6.latest-to-5.0.0.sql ``` Ibexa Open Source If you don't have access to Ibexa DXP's `ibexa/installer` package, apply the following database update: **MySQL** ``` -- Rename core related schema ALTER TABLE ezbinaryfile RENAME TO ibexa_binary_file; ALTER TABLE ezcobj_state RENAME TO ibexa_object_state; ALTER TABLE ibexa_object_state RENAME INDEX ezcobj_state_priority TO ibexa_object_state_priority; ALTER TABLE ibexa_object_state RENAME INDEX ezcobj_state_lmask TO ibexa_object_state_lmask; ALTER TABLE ibexa_object_state RENAME INDEX ezcobj_state_identifier TO ibexa_object_state_identifier; ALTER TABLE ezcobj_state_group RENAME TO ibexa_object_state_group; ALTER TABLE ibexa_object_state_group RENAME INDEX ezcobj_state_group_lmask TO ibexa_object_state_group_lmask; ALTER TABLE ibexa_object_state_group RENAME INDEX ezcobj_state_group_identifier TO ibexa_object_state_group_identifier; ALTER TABLE ezcobj_state_group_language RENAME TO ibexa_object_state_group_language; ALTER TABLE ezcobj_state_language RENAME TO ibexa_object_state_language; ALTER TABLE ezcobj_state_link RENAME TO ibexa_object_state_link; ALTER TABLE ezcontent_language RENAME TO ibexa_content_language; ALTER TABLE ibexa_content_language RENAME INDEX ezcontent_language_name TO ibexa_content_language_name; ALTER TABLE ezcontentbrowsebookmark RENAME TO ibexa_content_bookmark; ALTER TABLE ibexa_content_bookmark RENAME INDEX ezcontentbrowsebookmark_location TO ibexa_content_bookmark_location; ALTER TABLE ibexa_content_bookmark RENAME INDEX ezcontentbrowsebookmark_user TO ibexa_content_bookmark_user; ALTER TABLE ibexa_content_bookmark RENAME INDEX ezcontentbrowsebookmark_user_location TO ibexa_content_bookmark_user_location; ALTER TABLE ezcontentclass RENAME TO ibexa_content_type; ALTER TABLE ibexa_content_type RENAME INDEX ezcontentclass_version TO ibexa_content_type_version; ALTER TABLE ibexa_content_type RENAME INDEX ezcontentclass_identifier TO ibexa_content_type_identifier; ALTER TABLE ezcontentclass_attribute RENAME TO ibexa_content_type_field_definition; ALTER TABLE ibexa_content_type_field_definition RENAME INDEX ezcontentclass_attr_ccid TO ibexa_content_type_field_definition_ct_id; ALTER TABLE ibexa_content_type_field_definition RENAME INDEX ezcontentclass_attr_dts TO ibexa_content_type_field_definition_dts; ALTER TABLE ezcontentclass_attribute_ml RENAME TO ibexa_content_type_field_definition_ml; ALTER TABLE ibexa_content_type_field_definition_ml DROP FOREIGN KEY ezcontentclass_attribute_ml_lang_fk; ALTER TABLE ibexa_content_type_field_definition_ml ADD CONSTRAINT ibexa_content_type_field_definition_ml_lang_fk FOREIGN KEY (language_id) REFERENCES ibexa_content_language(id) ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE ezcontentclass_classgroup RENAME TO ibexa_content_type_group_assignment; ALTER TABLE ezcontentclass_name RENAME TO ibexa_content_type_name; ALTER TABLE ezcontentclassgroup RENAME TO ibexa_content_type_group; ALTER TABLE ezcontentobject_tree RENAME TO ibexa_content_tree; ALTER TABLE ibexa_content_tree RENAME INDEX ezcontentobject_tree_p_node_id TO ibexa_content_tree_p_node_id; ALTER TABLE ibexa_content_tree RENAME INDEX ezcontentobject_tree_path_ident TO ibexa_content_tree_path_ident; ALTER TABLE ibexa_content_tree RENAME INDEX ezcontentobject_tree_contentobject_id_path_string TO ibexa_content_tree_contentobject_id_path_string; ALTER TABLE ibexa_content_tree RENAME INDEX ezcontentobject_tree_co_id TO ibexa_content_tree_co_id; ALTER TABLE ibexa_content_tree RENAME INDEX ezcontentobject_tree_depth TO ibexa_content_tree_depth; ALTER TABLE ibexa_content_tree RENAME INDEX ezcontentobject_tree_path TO ibexa_content_tree_path; ALTER TABLE ibexa_content_tree RENAME INDEX modified_subnode TO ibexa_content_modified_subnode; ALTER TABLE ibexa_content_tree RENAME INDEX ezcontentobject_tree_remote_id TO ibexa_content_tree_remote_id; ALTER TABLE ibexa_content_bookmark DROP FOREIGN KEY ezcontentbrowsebookmark_location_fk; ALTER TABLE ibexa_content_bookmark ADD CONSTRAINT ibexa_content_bookmark_location_fk FOREIGN KEY (node_id) REFERENCES ibexa_content_tree(node_id) ON DELETE CASCADE; ALTER TABLE ezcontentobject RENAME TO ibexa_content; ALTER TABLE ibexa_content RENAME INDEX ezcontentobject_classid TO ibexa_content_type_id; ALTER TABLE ibexa_content RENAME INDEX ezcontentobject_lmask TO ibexa_content_lmask; ALTER TABLE ibexa_content RENAME INDEX ezcontentobject_pub TO ibexa_content_pub; ALTER TABLE ibexa_content RENAME INDEX ezcontentobject_section TO ibexa_content_section; ALTER TABLE ibexa_content RENAME INDEX ezcontentobject_currentversion TO ibexa_content_currentversion; ALTER TABLE ibexa_content RENAME INDEX ezcontentobject_owner TO ibexa_content_owner; ALTER TABLE ibexa_content RENAME INDEX ezcontentobject_status TO ibexa_content_status; ALTER TABLE ibexa_content RENAME INDEX ezcontentobject_remote_id TO ibexa_content_remote_id; ALTER TABLE ezcontentobject_attribute RENAME TO ibexa_content_field; ALTER TABLE ibexa_content_field RENAME INDEX ezcontentobject_attribute_co_id_ver_lang_code TO ibexa_content_field_co_id_ver_lang_code; ALTER TABLE ibexa_content_field RENAME INDEX ezcontentobject_classattr_id TO ibexa_content_field_classattr_id; ALTER TABLE ibexa_content_field RENAME INDEX ezcontentobject_attribute_language_code TO ibexa_content_field_language_code; ALTER TABLE ibexa_content_field RENAME INDEX ezcontentobject_attribute_co_id_ver TO ibexa_content_field_co_id_ver; ALTER TABLE ezcontentobject_link RENAME TO ibexa_content_relation; ALTER TABLE ibexa_content_relation RENAME INDEX ezco_link_to_co_id TO ibexa_content_relation_to_co_id; ALTER TABLE ibexa_content_relation RENAME INDEX ezco_link_from TO ibexa_content_relation_from; ALTER TABLE ibexa_content_relation RENAME INDEX ezco_link_cca_id TO ibexa_content_relation_cca_id; ALTER TABLE ezcontentobject_name RENAME TO ibexa_content_name; ALTER TABLE ibexa_content_name RENAME INDEX ezcontentobject_name_lang_id TO ibexa_content_name_lang_id; ALTER TABLE ibexa_content_name RENAME INDEX ezcontentobject_name_cov_id TO ibexa_content_name_cov_id; ALTER TABLE ibexa_content_name RENAME INDEX ezcontentobject_name_name TO ibexa_content_name_name; ALTER TABLE ezcontentobject_trash RENAME TO ibexa_content_trash; ALTER TABLE ibexa_content_trash RENAME INDEX ezcobj_trash_depth TO ibexa_content_trash_depth; ALTER TABLE ibexa_content_trash RENAME INDEX ezcobj_trash_p_node_id TO ibexa_content_trash_p_node_id; ALTER TABLE ibexa_content_trash RENAME INDEX ezcobj_trash_path_ident TO ibexa_content_trash_path_ident; ALTER TABLE ibexa_content_trash RENAME INDEX ezcobj_trash_co_id TO ibexa_content_trash_co_id; ALTER TABLE ibexa_content_trash RENAME INDEX ezcobj_trash_modified_subnode TO ibexa_content_trash_modified_subnode; ALTER TABLE ibexa_content_trash RENAME INDEX ezcobj_trash_path TO ibexa_content_trash_path; ALTER TABLE ezcontentobject_version RENAME TO ibexa_content_version; ALTER TABLE ibexa_content_version RENAME INDEX ezcobj_version_status TO ibexa_content_version_status; ALTER TABLE ibexa_content_version RENAME INDEX idx_object_version_objver TO ibexa_content_version_idx_ver; ALTER TABLE ibexa_content_version RENAME INDEX ezcontobj_version_obj_status TO ibexa_content_version_idx_status; ALTER TABLE ibexa_content_version RENAME INDEX ezcobj_version_creator_id TO ibexa_content_version_creator_id; ALTER TABLE ezdfsfile RENAME TO ibexa_dfs_file; ALTER TABLE ibexa_dfs_file RENAME INDEX ezdfsfile_name_trunk TO ibexa_dfs_file_name_trunk; ALTER TABLE ibexa_dfs_file RENAME INDEX ezdfsfile_expired_name TO ibexa_dfs_file_expired_name; ALTER TABLE ibexa_dfs_file RENAME INDEX ezdfsfile_name TO ibexa_dfs_file_name; ALTER TABLE ibexa_dfs_file RENAME INDEX ezdfsfile_mtime TO ibexa_dfs_file_mtime; ALTER TABLE ezgmaplocation RENAME TO ibexa_map_location; ALTER TABLE ibexa_map_location RENAME INDEX latitude_longitude_key TO ibexa_map_location_latitude_longitude_key; ALTER TABLE ezimagefile RENAME TO ibexa_image_file; ALTER TABLE ibexa_image_file RENAME INDEX ezimagefile_file TO ibexa_image_file_file; ALTER TABLE ibexa_image_file RENAME INDEX ezimagefile_coid TO ibexa_image_file_coid; ALTER TABLE ezkeyword RENAME TO ibexa_keyword; ALTER TABLE ibexa_keyword RENAME INDEX ezkeyword_keyword TO ibexa_keyword_keyword; ALTER TABLE ezkeyword_attribute_link RENAME TO ibexa_keyword_field_link; ALTER TABLE ibexa_keyword_field_link RENAME INDEX ezkeyword_attr_link_oaid TO ibexa_keyword_field_link_oaid; ALTER TABLE ibexa_keyword_field_link RENAME INDEX ezkeyword_attr_link_kid_oaid TO ibexa_keyword_field_link_kid_oaid; ALTER TABLE ibexa_keyword_field_link RENAME INDEX ezkeyword_attr_link_oaid_ver TO ibexa_keyword_field_link_oaid_ver; ALTER TABLE ezmedia RENAME TO ibexa_media; ALTER TABLE eznode_assignment RENAME TO ibexa_node_assignment; ALTER TABLE ibexa_node_assignment RENAME INDEX eznode_assignment_is_main TO ibexa_node_assignment_is_main; ALTER TABLE ibexa_node_assignment RENAME INDEX eznode_assignment_coid_cov TO ibexa_node_assignment_coid_cov; ALTER TABLE ibexa_node_assignment RENAME INDEX eznode_assignment_parent_node TO ibexa_node_assignment_parent_node; ALTER TABLE ibexa_node_assignment RENAME INDEX eznode_assignment_co_version TO ibexa_node_assignment_co_version; ALTER TABLE eznotification RENAME TO ibexa_notification; ALTER TABLE ibexa_notification RENAME INDEX eznotification_owner_is_pending TO ibexa_notification_owner_is_pending; ALTER TABLE ibexa_notification RENAME INDEX eznotification_owner TO ibexa_notification_owner; ALTER TABLE ezpackage RENAME TO ibexa_package; ALTER TABLE ezpolicy RENAME TO ibexa_policy; ALTER TABLE ibexa_policy RENAME INDEX ezpolicy_role_id TO ibexa_policy_role_id; ALTER TABLE ibexa_policy RENAME INDEX ezpolicy_original_id TO ibexa_policy_original_id; ALTER TABLE ezpolicy_limitation RENAME TO ibexa_policy_limitation; ALTER TABLE ibexa_policy_limitation RENAME INDEX policy_id TO ibexa_policy_id; ALTER TABLE ezpolicy_limitation_value RENAME TO ibexa_policy_limitation_value; ALTER TABLE ibexa_policy_limitation_value RENAME INDEX ezpolicy_limit_value_limit_id TO ibexa_policy_limit_value_limit_id; ALTER TABLE ibexa_policy_limitation_value RENAME INDEX ezpolicy_limitation_value_val TO ibexa_policy_limitation_value_val; ALTER TABLE ezpreferences RENAME TO ibexa_user_preference; ALTER TABLE ibexa_user_preference RENAME INDEX ezpreferences_user_id_idx TO ibexa_user_preference_user_id_idx; ALTER TABLE ibexa_user_preference RENAME INDEX ezpreferences_name TO ibexa_user_preference_name; ALTER TABLE ezrole RENAME TO ibexa_role; ALTER TABLE ezsearch_object_word_link RENAME TO ibexa_search_object_word_link; ALTER TABLE ibexa_search_object_word_link RENAME INDEX ezsearch_object_word_link_object TO ibexa_search_object_word_link_object; ALTER TABLE ibexa_search_object_word_link RENAME INDEX ezsearch_object_word_link_identifier TO ibexa_search_object_word_link_identifier; ALTER TABLE ibexa_search_object_word_link RENAME INDEX ezsearch_object_word_link_integer_value TO ibexa_search_object_word_link_integer_value; ALTER TABLE ibexa_search_object_word_link RENAME INDEX ezsearch_object_word_link_word TO ibexa_search_object_word_link_word; ALTER TABLE ibexa_search_object_word_link RENAME INDEX ezsearch_object_word_link_frequency TO ibexa_search_object_word_link_frequency; ALTER TABLE ezsearch_word RENAME TO ibexa_search_word; ALTER TABLE ibexa_search_word RENAME INDEX ezsearch_word_word_i TO ibexa_search_word_word_i; ALTER TABLE ibexa_search_word RENAME INDEX ezsearch_word_obj_count TO ibexa_search_word_obj_count; ALTER TABLE ezsection RENAME TO ibexa_section; ALTER TABLE ezsite_data RENAME TO ibexa_site_data; ALTER TABLE ezurl RENAME TO ibexa_url; ALTER TABLE ibexa_url RENAME INDEX ezurl_url TO ibexa_url_url; ALTER TABLE ezurl_object_link RENAME TO ibexa_url_content_link; ALTER TABLE ibexa_url_content_link RENAME INDEX ezurl_ol_coa_id TO ibexa_url_ol_coa_id; ALTER TABLE ibexa_url_content_link RENAME INDEX ezurl_ol_url_id TO ibexa_url_ol_url_id; ALTER TABLE ibexa_url_content_link RENAME INDEX ezurl_ol_coa_version TO ibexa_url_ol_coa_version; ALTER TABLE ibexa_url_content_link RENAME INDEX ezurl_ol_coa_id_cav TO ibexa_url_ol_coa_id_cav; ALTER TABLE ezurlalias RENAME TO ibexa_url_alias; ALTER TABLE ibexa_url_alias RENAME INDEX ezurlalias_source_md5 TO ibexa_url_alias_source_md5; ALTER TABLE ibexa_url_alias RENAME INDEX ezurlalias_wcard_fwd TO ibexa_url_alias_wcard_fwd; ALTER TABLE ibexa_url_alias RENAME INDEX ezurlalias_forward_to_id TO ibexa_url_alias_forward_to_id; ALTER TABLE ibexa_url_alias RENAME INDEX ezurlalias_imp_wcard_fwd TO ibexa_url_alias_imp_wcard_fwd; ALTER TABLE ibexa_url_alias RENAME INDEX ezurlalias_source_url TO ibexa_url_alias_source_url; ALTER TABLE ibexa_url_alias RENAME INDEX ezurlalias_desturl TO ibexa_url_alias_desturl; ALTER TABLE ezurlalias_ml RENAME TO ibexa_url_alias_ml; ALTER TABLE ibexa_url_alias_ml RENAME INDEX ezurlalias_ml_actt_org_al TO ibexa_url_alias_ml_actt_org_al; ALTER TABLE ibexa_url_alias_ml RENAME INDEX ezurlalias_ml_text_lang TO ibexa_url_alias_ml_text_lang; ALTER TABLE ibexa_url_alias_ml RENAME INDEX ezurlalias_ml_par_act_id_lnk TO ibexa_url_alias_ml_par_act_id_lnk; ALTER TABLE ibexa_url_alias_ml RENAME INDEX ezurlalias_ml_par_lnk_txt TO ibexa_url_alias_ml_par_lnk_txt; ALTER TABLE ibexa_url_alias_ml RENAME INDEX ezurlalias_ml_act_org TO ibexa_url_alias_ml_act_org; ALTER TABLE ibexa_url_alias_ml RENAME INDEX ezurlalias_ml_text TO ibexa_url_alias_ml_text; ALTER TABLE ibexa_url_alias_ml RENAME INDEX ezurlalias_ml_link TO ibexa_url_alias_ml_link; ALTER TABLE ibexa_url_alias_ml RENAME INDEX ezurlalias_ml_id TO ibexa_url_alias_ml_id; ALTER TABLE ezurlalias_ml_incr RENAME TO ibexa_url_alias_ml_incr; ALTER TABLE ezurlwildcard RENAME TO ibexa_url_wildcard; ALTER TABLE ezuser RENAME TO ibexa_user; ALTER TABLE ibexa_user RENAME INDEX ezuser_login TO ibexa_user_login; ALTER TABLE ezuser_accountkey RENAME TO ibexa_user_accountkey; ALTER TABLE ezuser_role RENAME TO ibexa_user_role; ALTER TABLE ibexa_user_role RENAME INDEX ezuser_role_role_id TO ibexa_user_role_role_id; ALTER TABLE ibexa_user_role RENAME INDEX ezuser_role_contentobject_id TO ibexa_user_role_contentobject_id; ALTER TABLE ezuser_setting RENAME TO ibexa_user_setting; ALTER TABLE ibexa_content_bookmark DROP FOREIGN KEY ezcontentbrowsebookmark_user_fk; ALTER TABLE ibexa_content_bookmark ADD CONSTRAINT ibexa_content_bookmark_user_fk FOREIGN KEY (user_id) REFERENCES ibexa_user(contentobject_id) ON DELETE CASCADE; -- Rename contentclass_id column ALTER TABLE ibexa_content_type_field_definition RENAME COLUMN contentclass_id TO content_type_id; ALTER TABLE ibexa_content_type_group_assignment RENAME COLUMN contentclass_id TO content_type_id; ALTER TABLE ibexa_content_type_name RENAME COLUMN contentclass_id TO content_type_id; ALTER TABLE ibexa_content RENAME COLUMN contentclass_id TO content_type_id; ALTER TABLE ibexa_search_object_word_link RENAME COLUMN contentclass_id TO content_type_id; -- Update content type version to status ALTER TABLE ibexa_content_type RENAME INDEX ibexa_content_type_version TO ibexa_content_type_status; ALTER TABLE ibexa_content_type RENAME COLUMN version TO status; ALTER TABLE ibexa_content_type_field_definition RENAME COLUMN version TO status; ALTER TABLE ibexa_content_type_field_definition_ml RENAME COLUMN version TO status; ALTER TABLE ibexa_content_type_group_assignment RENAME COLUMN contentclass_version TO content_type_status; ALTER TABLE ibexa_content_type_name RENAME COLUMN contentclass_version TO content_type_status; -- Rename user invitations tables ALTER TABLE ibexa_user_invitations RENAME TO ibexa_user_invitation; ALTER TABLE ibexa_user_invitation RENAME INDEX ibexa_user_invitations_email_idx TO ibexa_user_invitation_email_idx; ALTER TABLE ibexa_user_invitation RENAME INDEX ibexa_user_invitations_hash_idx TO ibexa_user_invitation_hash_idx; ALTER TABLE ibexa_user_invitation RENAME INDEX ibexa_user_invitations_email_uindex TO ibexa_user_invitation_email_uindex; ALTER TABLE ibexa_user_invitation RENAME INDEX ibexa_user_invitations_hash_uindex TO ibexa_user_invitation_hash_uindex; ALTER TABLE ibexa_user_invitations_assignments RENAME TO ibexa_user_invitation_assignment; ALTER TABLE ibexa_user_invitation_assignment DROP FOREIGN KEY ibexa_user_invitations_assignments_ibexa_user_invitations_id_fk; ALTER TABLE ibexa_user_invitation_assignment ADD CONSTRAINT ibexa_user_invitation_assignment_ibexa_user_invitation_id_fk FOREIGN KEY (invitation_id) REFERENCES ibexa_user_invitation(id) ON DELETE CASCADE ON UPDATE CASCADE; -- Rename content type field definition ML columns ALTER TABLE ibexa_content_type_field_definition_ml RENAME COLUMN contentclass_attribute_id TO content_type_field_definition_id; -- Rename content field columns and indexes ALTER TABLE ibexa_content_field RENAME COLUMN contentclassattribute_id TO content_type_field_definition_id; ALTER TABLE ibexa_content_field RENAME INDEX ibexa_content_field_classattr_id TO ibexa_content_field_field_definition_id; -- Update content relation columns and indexes ALTER TABLE ibexa_content_relation RENAME COLUMN contentclassattribute_id TO content_type_field_definition_id; ALTER TABLE ibexa_content_relation RENAME INDEX ibexa_content_relation_cca_id TO ibexa_content_relation_ccfd_id; -- Update search object word link columns ALTER TABLE ibexa_search_object_word_link RENAME COLUMN contentclass_attribute_id TO content_type_field_definition_id; ``` **PostgreSQL** ``` -- Rename core related schema ALTER TABLE ezbinaryfile RENAME TO ibexa_binary_file; ALTER TABLE ezcobj_state RENAME TO ibexa_object_state; ALTER INDEX ezcobj_state_priority RENAME TO ibexa_object_state_priority; ALTER INDEX ezcobj_state_lmask RENAME TO ibexa_object_state_lmask; ALTER INDEX ezcobj_state_identifier RENAME TO ibexa_object_state_identifier; ALTER TABLE ezcobj_state_group RENAME TO ibexa_object_state_group; ALTER INDEX ezcobj_state_group_lmask RENAME TO ibexa_object_state_group_lmask; ALTER INDEX ezcobj_state_group_identifier RENAME TO ibexa_object_state_group_identifier; ALTER TABLE ezcobj_state_group_language RENAME TO ibexa_object_state_group_language; ALTER TABLE ezcobj_state_language RENAME TO ibexa_object_state_language; ALTER TABLE ezcobj_state_link RENAME TO ibexa_object_state_link; ALTER TABLE ezcontent_language RENAME TO ibexa_content_language; ALTER INDEX ezcontent_language_name RENAME TO ibexa_content_language_name; ALTER TABLE ezcontentbrowsebookmark RENAME TO ibexa_content_bookmark; ALTER INDEX ezcontentbrowsebookmark_location RENAME TO ibexa_content_bookmark_location; ALTER INDEX ezcontentbrowsebookmark_user RENAME TO ibexa_content_bookmark_user; ALTER INDEX ezcontentbrowsebookmark_user_location RENAME TO ibexa_content_bookmark_user_location; ALTER TABLE ezcontentclass RENAME TO ibexa_content_type; ALTER INDEX ezcontentclass_version RENAME TO ibexa_content_type_version; ALTER INDEX ezcontentclass_identifier RENAME TO ibexa_content_type_identifier; ALTER TABLE ezcontentclass_attribute RENAME TO ibexa_content_type_field_definition; ALTER INDEX ezcontentclass_attr_ccid RENAME TO ibexa_content_type_field_definition_ct_id; ALTER INDEX ezcontentclass_attr_dts RENAME TO ibexa_content_type_field_definition_dts; ALTER TABLE ezcontentclass_attribute_ml RENAME TO ibexa_content_type_field_definition_ml; ALTER TABLE ibexa_content_type_field_definition_ml DROP CONSTRAINT ezcontentclass_attribute_ml_lang_fk; ALTER TABLE ibexa_content_type_field_definition_ml ADD CONSTRAINT ibexa_content_type_field_definition_ml_lang_fk FOREIGN KEY (language_id) REFERENCES ibexa_content_language(id) ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE ezcontentclass_classgroup RENAME TO ibexa_content_type_group_assignment; ALTER TABLE ezcontentclass_name RENAME TO ibexa_content_type_name; ALTER TABLE ezcontentclassgroup RENAME TO ibexa_content_type_group; ALTER TABLE ezcontentobject_tree RENAME TO ibexa_content_tree; ALTER INDEX ezcontentobject_tree_p_node_id RENAME TO ibexa_content_tree_p_node_id; ALTER INDEX ezcontentobject_tree_path_ident RENAME TO ibexa_content_tree_path_ident; ALTER INDEX ezcontentobject_tree_contentobject_id_path_string RENAME TO ibexa_content_tree_contentobject_id_path_string; ALTER INDEX ezcontentobject_tree_co_id RENAME TO ibexa_content_tree_co_id; ALTER INDEX ezcontentobject_tree_depth RENAME TO ibexa_content_tree_depth; ALTER INDEX ezcontentobject_tree_path RENAME TO ibexa_content_tree_path; ALTER INDEX modified_subnode RENAME TO ibexa_content_modified_subnode; ALTER INDEX ezcontentobject_tree_remote_id RENAME TO ibexa_content_tree_remote_id; ALTER TABLE ibexa_content_bookmark DROP CONSTRAINT ezcontentbrowsebookmark_location_fk; ALTER TABLE ibexa_content_bookmark ADD CONSTRAINT ibexa_content_bookmark_location_fk FOREIGN KEY (node_id) REFERENCES ibexa_content_tree(node_id) ON DELETE CASCADE; ALTER TABLE ezcontentobject RENAME TO ibexa_content; ALTER INDEX ezcontentobject_classid RENAME TO ibexa_content_type_id; ALTER INDEX ezcontentobject_lmask RENAME TO ibexa_content_lmask; ALTER INDEX ezcontentobject_pub RENAME TO ibexa_content_pub; ALTER INDEX ezcontentobject_section RENAME TO ibexa_content_section; ALTER INDEX ezcontentobject_currentversion RENAME TO ibexa_content_currentversion; ALTER INDEX ezcontentobject_owner RENAME TO ibexa_content_owner; ALTER INDEX ezcontentobject_status RENAME TO ibexa_content_status; ALTER INDEX ezcontentobject_remote_id RENAME TO ibexa_content_remote_id; ALTER TABLE ezcontentobject_attribute RENAME TO ibexa_content_field; ALTER INDEX ezcontentobject_attribute_co_id_ver_lang_code RENAME TO ibexa_content_field_co_id_ver_lang_code; ALTER INDEX ezcontentobject_classattr_id RENAME TO ibexa_content_field_classattr_id; ALTER INDEX ezcontentobject_attribute_language_code RENAME TO ibexa_content_field_language_code; ALTER INDEX ezcontentobject_attribute_co_id_ver RENAME TO ibexa_content_field_co_id_ver; ALTER TABLE ezcontentobject_link RENAME TO ibexa_content_relation; ALTER INDEX ezco_link_to_co_id RENAME TO ibexa_content_relation_to_co_id; ALTER INDEX ezco_link_from RENAME TO ibexa_content_relation_from; ALTER INDEX ezco_link_cca_id RENAME TO ibexa_content_relation_cca_id; ALTER TABLE ezcontentobject_name RENAME TO ibexa_content_name; ALTER INDEX ezcontentobject_name_lang_id RENAME TO ibexa_content_name_lang_id; ALTER INDEX ezcontentobject_name_cov_id RENAME TO ibexa_content_name_cov_id; ALTER INDEX ezcontentobject_name_name RENAME TO ibexa_content_name_name; ALTER TABLE ezcontentobject_trash RENAME TO ibexa_content_trash; ALTER INDEX ezcobj_trash_depth RENAME TO ibexa_content_trash_depth; ALTER INDEX ezcobj_trash_p_node_id RENAME TO ibexa_content_trash_p_node_id; ALTER INDEX ezcobj_trash_path_ident RENAME TO ibexa_content_trash_path_ident; ALTER INDEX ezcobj_trash_co_id RENAME TO ibexa_content_trash_co_id; ALTER INDEX ezcobj_trash_modified_subnode RENAME TO ibexa_content_trash_modified_subnode; ALTER INDEX ezcobj_trash_path RENAME TO ibexa_content_trash_path; ALTER TABLE ezcontentobject_version RENAME TO ibexa_content_version; ALTER INDEX ezcobj_version_status RENAME TO ibexa_content_version_status; ALTER INDEX idx_object_version_objver RENAME TO ibexa_content_version_idx_ver; ALTER INDEX ezcontobj_version_obj_status RENAME TO ibexa_content_version_idx_status; ALTER INDEX ezcobj_version_creator_id RENAME TO ibexa_content_version_creator_id; ALTER TABLE ezdfsfile RENAME TO ibexa_dfs_file; ALTER INDEX ezdfsfile_name_trunk RENAME TO ibexa_dfs_file_name_trunk; ALTER INDEX ezdfsfile_expired_name RENAME TO ibexa_dfs_file_expired_name; ALTER INDEX ezdfsfile_name RENAME TO ibexa_dfs_file_name; ALTER INDEX ezdfsfile_mtime RENAME TO ibexa_dfs_file_mtime; ALTER TABLE ezgmaplocation RENAME TO ibexa_map_location; ALTER INDEX latitude_longitude_key RENAME TO ibexa_map_location_latitude_longitude_key; ALTER TABLE ezimagefile RENAME TO ibexa_image_file; ALTER INDEX ezimagefile_file RENAME TO ibexa_image_file_file; ALTER INDEX ezimagefile_coid RENAME TO ibexa_image_file_coid; ALTER TABLE ezkeyword RENAME TO ibexa_keyword; ALTER INDEX ezkeyword_keyword RENAME TO ibexa_keyword_keyword; ALTER TABLE ezkeyword_attribute_link RENAME TO ibexa_keyword_field_link; ALTER INDEX ezkeyword_attr_link_oaid RENAME TO ibexa_keyword_field_link_oaid; ALTER INDEX ezkeyword_attr_link_kid_oaid RENAME TO ibexa_keyword_field_link_kid_oaid; ALTER INDEX ezkeyword_attr_link_oaid_ver RENAME TO ibexa_keyword_field_link_oaid_ver; ALTER TABLE ezmedia RENAME TO ibexa_media; ALTER TABLE eznode_assignment RENAME TO ibexa_node_assignment; ALTER INDEX eznode_assignment_is_main RENAME TO ibexa_node_assignment_is_main; ALTER INDEX eznode_assignment_coid_cov RENAME TO ibexa_node_assignment_coid_cov; ALTER INDEX eznode_assignment_parent_node RENAME TO ibexa_node_assignment_parent_node; ALTER INDEX eznode_assignment_co_version RENAME TO ibexa_node_assignment_co_version; ALTER TABLE eznotification RENAME TO ibexa_notification; ALTER INDEX eznotification_owner_is_pending RENAME TO ibexa_notification_owner_is_pending; ALTER INDEX eznotification_owner RENAME TO ibexa_notification_owner; ALTER TABLE ezpackage RENAME TO ibexa_package; ALTER TABLE ezpolicy RENAME TO ibexa_policy; ALTER INDEX ezpolicy_role_id RENAME TO ibexa_policy_role_id; ALTER INDEX ezpolicy_original_id RENAME TO ibexa_policy_original_id; ALTER TABLE ezpolicy_limitation RENAME TO ibexa_policy_limitation; ALTER INDEX policy_id RENAME TO ibexa_policy_id; ALTER TABLE ezpolicy_limitation_value RENAME TO ibexa_policy_limitation_value; ALTER INDEX ezpolicy_limit_value_limit_id RENAME TO ibexa_policy_limit_value_limit_id; ALTER INDEX ezpolicy_limitation_value_val RENAME TO ibexa_policy_limitation_value_val; ALTER TABLE ezpreferences RENAME TO ibexa_user_preference; ALTER INDEX ezpreferences_user_id_idx RENAME TO ibexa_user_preference_user_id_idx; ALTER INDEX ezpreferences_name RENAME TO ibexa_user_preference_name; ALTER TABLE ezrole RENAME TO ibexa_role; ALTER TABLE ezsearch_object_word_link RENAME TO ibexa_search_object_word_link; ALTER INDEX ezsearch_object_word_link_object RENAME TO ibexa_search_object_word_link_object; ALTER INDEX ezsearch_object_word_link_identifier RENAME TO ibexa_search_object_word_link_identifier; ALTER INDEX ezsearch_object_word_link_integer_value RENAME TO ibexa_search_object_word_link_integer_value; ALTER INDEX ezsearch_object_word_link_word RENAME TO ibexa_search_object_word_link_word; ALTER INDEX ezsearch_object_word_link_frequency RENAME TO ibexa_search_object_word_link_frequency; ALTER TABLE ezsearch_word RENAME TO ibexa_search_word; ALTER INDEX ezsearch_word_word_i RENAME TO ibexa_search_word_word_i; ALTER INDEX ezsearch_word_obj_count RENAME TO ibexa_search_word_obj_count; ALTER TABLE ezsection RENAME TO ibexa_section; ALTER TABLE ezsite_data RENAME TO ibexa_site_data; ALTER TABLE ezurl RENAME TO ibexa_url; ALTER INDEX ezurl_url RENAME TO ibexa_url_url; ALTER TABLE ezurl_object_link RENAME TO ibexa_url_content_link; ALTER INDEX ezurl_ol_coa_id RENAME TO ibexa_url_ol_coa_id; ALTER INDEX ezurl_ol_url_id RENAME TO ibexa_url_ol_url_id; ALTER INDEX ezurl_ol_coa_version RENAME TO ibexa_url_ol_coa_version; ALTER INDEX ezurl_ol_coa_id_cav RENAME TO ibexa_url_ol_coa_id_cav; ALTER TABLE ezurlalias RENAME TO ibexa_url_alias; ALTER INDEX ezurlalias_source_md5 RENAME TO ibexa_url_alias_source_md5; ALTER INDEX ezurlalias_wcard_fwd RENAME TO ibexa_url_alias_wcard_fwd; ALTER INDEX ezurlalias_forward_to_id RENAME TO ibexa_url_alias_forward_to_id; ALTER INDEX ezurlalias_imp_wcard_fwd RENAME TO ibexa_url_alias_imp_wcard_fwd; ALTER INDEX ezurlalias_source_url RENAME TO ibexa_url_alias_source_url; ALTER INDEX ezurlalias_desturl RENAME TO ibexa_url_alias_desturl; ALTER TABLE ezurlalias_ml RENAME TO ibexa_url_alias_ml; ALTER INDEX ezurlalias_ml_actt_org_al RENAME TO ibexa_url_alias_ml_actt_org_al; ALTER INDEX ezurlalias_ml_text_lang RENAME TO ibexa_url_alias_ml_text_lang; ALTER INDEX ezurlalias_ml_par_act_id_lnk RENAME TO ibexa_url_alias_ml_par_act_id_lnk; ALTER INDEX ezurlalias_ml_par_lnk_txt RENAME TO ibexa_url_alias_ml_par_lnk_txt; ALTER INDEX ezurlalias_ml_act_org RENAME TO ibexa_url_alias_ml_act_org; ALTER INDEX ezurlalias_ml_text RENAME TO ibexa_url_alias_ml_text; ALTER INDEX ezurlalias_ml_link RENAME TO ibexa_url_alias_ml_link; ALTER INDEX ezurlalias_ml_id RENAME TO ibexa_url_alias_ml_id; ALTER TABLE ezurlalias_ml_incr RENAME TO ibexa_url_alias_ml_incr; ALTER TABLE ezurlwildcard RENAME TO ibexa_url_wildcard; ALTER TABLE ezuser RENAME TO ibexa_user; ALTER INDEX ezuser_login RENAME TO ibexa_user_login; ALTER TABLE ezuser_accountkey RENAME TO ibexa_user_accountkey; ALTER TABLE ezuser_role RENAME TO ibexa_user_role; ALTER INDEX ezuser_role_role_id RENAME TO ibexa_user_role_role_id; ALTER INDEX ezuser_role_contentobject_id RENAME TO ibexa_user_role_contentobject_id; ALTER TABLE ezuser_setting RENAME TO ibexa_user_setting; ALTER TABLE ibexa_content_bookmark DROP CONSTRAINT ezcontentbrowsebookmark_user_fk; ALTER TABLE ibexa_content_bookmark ADD CONSTRAINT ibexa_content_bookmark_user_fk FOREIGN KEY (user_id) REFERENCES ibexa_user(contentobject_id) ON DELETE CASCADE; -- Rename contentclass_id column ALTER TABLE ibexa_content_type_field_definition RENAME COLUMN contentclass_id TO content_type_id; ALTER TABLE ibexa_content_type_group_assignment RENAME COLUMN contentclass_id TO content_type_id; ALTER TABLE ibexa_content_type_name RENAME COLUMN contentclass_id TO content_type_id; ALTER TABLE ibexa_content RENAME COLUMN contentclass_id TO content_type_id; ALTER TABLE ibexa_search_object_word_link RENAME COLUMN contentclass_id TO content_type_id; -- Update content type version to status ALTER INDEX ibexa_content_type_version RENAME TO ibexa_content_type_status; ALTER TABLE ibexa_content_type RENAME COLUMN version TO status; ALTER TABLE ibexa_content_type_field_definition RENAME COLUMN version TO status; ALTER TABLE ibexa_content_type_field_definition_ml RENAME COLUMN version TO status; ALTER TABLE ibexa_content_type_group_assignment RENAME COLUMN contentclass_version TO content_type_status; ALTER TABLE ibexa_content_type_name RENAME COLUMN contentclass_version TO content_type_status; -- Rename user invitations tables ALTER TABLE ibexa_user_invitations RENAME TO ibexa_user_invitation; ALTER INDEX ibexa_user_invitations_email_idx RENAME TO ibexa_user_invitation_email_idx; ALTER INDEX ibexa_user_invitations_hash_idx RENAME TO ibexa_user_invitation_hash_idx; ALTER INDEX ibexa_user_invitations_email_uindex RENAME TO ibexa_user_invitation_email_uindex; ALTER INDEX ibexa_user_invitations_hash_uindex RENAME TO ibexa_user_invitation_hash_uindex; ALTER TABLE ibexa_user_invitations_assignments RENAME TO ibexa_user_invitation_assignment; ALTER TABLE ibexa_user_invitation_assignment DROP CONSTRAINT ibexa_user_invitations_assignments_ibexa_user_invitations_id_fk; ALTER TABLE ibexa_user_invitation_assignment ADD CONSTRAINT ibexa_user_invitation_assignment_ibexa_user_invitation_id_fk FOREIGN KEY (invitation_id) REFERENCES ibexa_user_invitation(id) ON DELETE CASCADE ON UPDATE CASCADE; -- Rename content type field definition ML columns ALTER TABLE ibexa_content_type_field_definition_ml RENAME COLUMN contentclass_attribute_id TO content_type_field_definition_id; -- Rename content field columns and indexes ALTER TABLE ibexa_content_field RENAME COLUMN contentclassattribute_id TO content_type_field_definition_id; ALTER INDEX ibexa_content_field_classattr_id RENAME TO ibexa_content_field_field_definition_id; -- Update content relation columns and indexes ALTER TABLE ibexa_content_relation RENAME COLUMN contentclassattribute_id TO content_type_field_definition_id; ALTER INDEX ibexa_content_relation_cca_id RENAME TO ibexa_content_relation_ccfd_id; -- Update search object word link columns ALTER TABLE ibexa_search_object_word_link RENAME COLUMN contentclass_attribute_id TO content_type_field_definition_id; -- Rename core sequence names to match new table names ALTER SEQUENCE ezcobj_state_group_id_seq RENAME TO ibexa_object_state_group_id_seq; ALTER SEQUENCE ezcobj_state_id_seq RENAME TO ibexa_object_state_id_seq; ALTER SEQUENCE ezcontentbrowsebookmark_id_seq RENAME TO ibexa_content_bookmark_id_seq; ALTER SEQUENCE ezcontentclass_attribute_id_seq RENAME TO ibexa_content_type_field_definition_id_seq; ALTER SEQUENCE ezcontentclass_id_seq RENAME TO ibexa_content_type_id_seq; ALTER SEQUENCE ezcontentclassgroup_id_seq RENAME TO ibexa_content_type_group_id_seq; ALTER SEQUENCE ezcontentobject_attribute_id_seq RENAME TO ibexa_content_field_id_seq; ALTER SEQUENCE ezcontentobject_id_seq RENAME TO ibexa_content_id_seq; ALTER SEQUENCE ezcontentobject_link_id_seq RENAME TO ibexa_content_relation_id_seq; ALTER SEQUENCE ezcontentobject_tree_node_id_seq RENAME TO ibexa_content_tree_node_id_seq; ALTER SEQUENCE ezcontentobject_version_id_seq RENAME TO ibexa_content_version_id_seq; ALTER SEQUENCE ezimagefile_id_seq RENAME TO ibexa_image_file_id_seq; ALTER SEQUENCE ezkeyword_attribute_link_id_seq RENAME TO ibexa_keyword_field_link_id_seq; ALTER SEQUENCE ezkeyword_id_seq RENAME TO ibexa_keyword_id_seq; ALTER SEQUENCE eznode_assignment_id_seq RENAME TO ibexa_node_assignment_id_seq; ALTER SEQUENCE eznotification_id_seq RENAME TO ibexa_notification_id_seq; ALTER SEQUENCE ezpackage_id_seq RENAME TO ibexa_package_id_seq; ALTER SEQUENCE ezpolicy_id_seq RENAME TO ibexa_policy_id_seq; ALTER SEQUENCE ezpolicy_limitation_id_seq RENAME TO ibexa_policy_limitation_id_seq; ALTER SEQUENCE ezpolicy_limitation_value_id_seq RENAME TO ibexa_policy_limitation_value_id_seq; ALTER SEQUENCE ezpreferences_id_seq RENAME TO ibexa_user_preference_id_seq; ALTER SEQUENCE ezrole_id_seq RENAME TO ibexa_role_id_seq; ALTER SEQUENCE ezsearch_object_word_link_id_seq RENAME TO ibexa_search_object_word_link_id_seq; ALTER SEQUENCE ezsearch_word_id_seq RENAME TO ibexa_search_word_id_seq; ALTER SEQUENCE ezsection_id_seq RENAME TO ibexa_section_id_seq; ALTER SEQUENCE ezurl_id_seq RENAME TO ibexa_url_id_seq; ALTER SEQUENCE ezurlalias_id_seq RENAME TO ibexa_url_alias_id_seq; ALTER SEQUENCE ezurlalias_ml_incr_id_seq RENAME TO ibexa_url_alias_ml_incr_id_seq; ALTER SEQUENCE ezurlwildcard_id_seq RENAME TO ibexa_url_wildcard_id_seq; ALTER SEQUENCE ezuser_accountkey_id_seq RENAME TO ibexa_user_accountkey_id_seq; ALTER SEQUENCE ezuser_role_id_seq RENAME TO ibexa_user_role_id_seq; ``` As this script targets all editions, on editions lower than Commerce you may encounter errors about missing tables which can safely be ignored. Many tables and columns are renamed. If you have custom code directly querying those, you will need to update them. You can track the renaming in the `ibexa-4.6.latest-to-5.0.0.sql` file or below. Tables and columns renaming map | Old name | New name | | ----------------------------------------------------- | ----------------------------------------------------------------------- | | ezbinaryfile | ibexa_binary_file | | ezcobj_state | ibexa_object_state | | ezcobj_state_group | ibexa_object_state_group | | ezcobj_state_group_language | ibexa_object_state_group_language | | ezcobj_state_language | ibexa_object_state_language | | ezcobj_state_link | ibexa_object_state_link | | ezcontent_language | ibexa_content_language | | ezcontentbrowsebookmark | ibexa_content_bookmark | | ezcontentclass | ibexa_content_type | | ezcontentclass_attribute | ibexa_content_type_field_definition | | ezcontentclass_attribute.contentclass_id | ibexa_content_type_field_definition.content_type_id | | ezcontentclass_attribute_ml | ibexa_content_type_field_definition_ml | | ezcontentclass_attribute_ml.contentclass_attribute_id | ibexa_content_type_field_definition_ml.content_type_field_definition_id | | ezcontentclass_classgroup | ibexa_content_type_group_assignment | | ezcontentclass_classgroup.contentclass_id | ibexa_content_type_group_assignment.content_type_id | | ezcontentclass_name | ibexa_content_type_name | | ezcontentclass_name.contentclass_id | ibexa_content_type_name.content_type_id | | ezcontentclassgroup | ibexa_content_type_group | | ezcontentobject | ibexa_content | | ezcontentobject.contentclass_id | ibexa_content.content_type_id | | ezcontentobject_attribute | ibexa_content_field | | ezcontentobject_attribute.contentclassattribute_id | ibexa_content_field.content_type_field_definition_id | | ezcontentobject_link | ibexa_content_relation | | ezcontentobject_link.contentclassattribute_id | ibexa_content_relation.content_type_field_definition_id | | ezcontentobject_name | ibexa_content_name | | ezcontentobject_trash | ibexa_content_trash | | ezcontentobject_tree | ibexa_content_tree | | ezcontentobject_version | ibexa_content_version | | ezdatebasedpublisher_scheduled_entries | ibexa_scheduler_scheduled_entries | | ezdfsfile | ibexa_dfs_file | | ezeditorialworkflow_markings | ibexa_workflow_markings | | ezeditorialworkflow_transitions | ibexa_workflow_transitions | | ezeditorialworkflow_workflows | ibexa_workflow_workflows | | ezform_field_attributes | ibexa_form_field_attributes | | ezform_field_validators | ibexa_form_field_validators | | ezform_fields | ibexa_form_fields | | ezform_form_submission_data | ibexa_form_form_submission_data | | ezform_form_submissions | ibexa_form_form_submissions | | ezform_forms | ibexa_form_forms | | ezgmaplocation | ibexa_map_location | | ezimagefile | ibexa_image_file | | ezkeyword | ibexa_keyword | | ezkeyword_attribute_link | ibexa_keyword_field_link | | ezmedia | ibexa_media | | eznode_assignment | ibexa_node_assignment | | eznotification | ibexa_notification | | ezpackage | ibexa_package | | ezpage_attributes | ibexa_page_attributes | | ezpage_blocks | ibexa_page_blocks | | ezpage_blocks_design | ibexa_page_blocks_design | | ezpage_blocks_visibility | ibexa_page_blocks_visibility | | ezpage_map_attributes_blocks | ibexa_page_map_attributes_blocks | | ezpage_map_blocks_zones | ibexa_page_map_blocks_zones | | ezpage_map_zones_pages | ibexa_page_map_zones_pages | | ezpage_pages | ibexa_page_pages | | ezpage_zones | ibexa_page_zones | | ezpolicy | ibexa_policy | | ezpolicy_limitation | ibexa_policy_limitation | | ezpolicy_limitation_value | ibexa_policy_limitation_value | | ezpreferences | ibexa_preferences | | ezrole | ibexa_role | | ezsearch_object_word_link | ibexa_search_object_word_link | | ezsearch_object_word_link.contentclass_id | ibexa_search_object_word_link.content_type_id | | ezsearch_object_word_link.contentclass_attribute_id | ibexa_search_object_word_link.content_type_field_definition_id | | ezsearch_word | ibexa_search_word | | ezsection | ibexa_section | | ezsite | ibexa_site | | ezsite_data | ibexa_site_data | | ezsite_public_access | ibexa_site_public_access | | ezurl | ibexa_url | | ezurl_object_link | ibexa_url_content_link | | ezurlalias | ibexa_url_alias | | ezurlalias_ml | ibexa_url_alias_ml | | ezurlalias_ml_incr | ibexa_url_alias_ml_incr | | ezurlwildcard | ibexa_url_wildcard | | ezuser | ibexa_user | | ezuser_accountkey | ibexa_user_accountkey | | ezuser_role | ibexa_user_role | | ezuser_setting | ibexa_user_setting | DFS (Distributed File System) If [DFS IO handler](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/clustering/clustering/#dfs-io-handler) is used and, as recommended, its table is on its own database, you'll have to rename table and columns there. Here are the DFS renaming queries (extracted from `ibexa-4.6.latest-to-5.0.0.sql`): ``` ALTER TABLE ezdfsfile RENAME TO ibexa_dfs_file; ALTER TABLE ibexa_dfs_file RENAME INDEX ezdfsfile_name_trunk TO ibexa_dfs_file_name_trunk; ALTER TABLE ibexa_dfs_file RENAME INDEX ezdfsfile_expired_name TO ibexa_dfs_file_expired_name; ALTER TABLE ibexa_dfs_file RENAME INDEX ezdfsfile_name TO ibexa_dfs_file_name; ALTER TABLE ibexa_dfs_file RENAME INDEX ezdfsfile_mtime TO ibexa_dfs_file_mtime; ``` ### Update custom code for Ibexa DXP 5.0 See [Ibexa DXP v5.0 deprecations and backwards compatibility breaks](https://doc.ibexa.co/en/latest/release_notes/ibexa_dxp_v5.0_deprecations/index.md) for the list of changes. The following sections presents some of those changes and how to apply them. #### Update PHP framework standards Among other things, previously deprecated classes have been removed, and the type hinting strictness has been increased. Update the `rector.php` file to use [`IbexaSetList::IBEXA_50`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Rector-Sets-IbexaSetList.html#enumcase_IBEXA_50) rule set. If you didn't edit it the first time, you can run its recipe: ``` composer recipe:install ibexa/rector --force --reset --yes ``` You can adjust the other rule sets (for example, the Symfony ones) to match higher versions. Again, it's recommended to activate one rule set at a time and preview the output by running Rector with the `--dry-run` option to decide which rulesets should be used and in which order. As this update spans across a broad range of versions, multiple rules can be considered as in the example below. ``` //… use Rector\Symfony\Set\SymfonySetList; use Rector\Symfony\Set\SensiolabsSetList; //… ->withSets( [ IbexaSetList::IBEXA_50->value, SymfonySetList::SYMFONY_54, // https://getrector.com/find-rule?activeRectorSetGroup=symfony&rectorSet=symfony-symfonysymfony-54 SymfonySetList::SYMFONY_60, // https://getrector.com/find-rule?activeRectorSetGroup=symfony&rectorSet=symfony-symfonysymfony-60 SymfonySetList::SYMFONY_61, // https://getrector.com/find-rule?activeRectorSetGroup=symfony&rectorSet=symfony-symfonysymfony-61 SymfonySetList::SYMFONY_62, // https://getrector.com/find-rule?activeRectorSetGroup=symfony&rectorSet=symfony-symfonysymfony-62 SymfonySetList::SYMFONY_63, // https://getrector.com/find-rule?activeRectorSetGroup=symfony&rectorSet=symfony-symfonysymfony-63 SymfonySetList::SYMFONY_64, // https://getrector.com/find-rule?activeRectorSetGroup=symfony&rectorSet=symfony-symfonysymfony-64 SymfonySetList::SYMFONY_70, // https://getrector.com/find-rule?activeRectorSetGroup=symfony&rectorSet=symfony-symfonysymfony-70 SymfonySetList::SYMFONY_71, // https://getrector.com/find-rule?activeRectorSetGroup=symfony&rectorSet=symfony-symfonysymfony-71 SymfonySetList::SYMFONY_72, // https://getrector.com/find-rule?activeRectorSetGroup=symfony&rectorSet=symfony-symfonysymfony-72 SymfonySetList::SYMFONY_73, // https://getrector.com/find-rule?activeRectorSetGroup=symfony&rectorSet=symfony-symfonysymfony-73 SymfonySetList::ANNOTATIONS_TO_ATTRIBUTES, SensiolabsSetList::ANNOTATIONS_TO_ATTRIBUTES, ] ) ->withPhpSets() ->withComposerBased(twig: true, symfony: true) ->withAttributesSets(symfony: true, sensiolabs: true) ->withPreparedSets( deadCode: true, // https://getrector.com/find-rule?activeRectorSetGroup=core&rectorSet=core-dead-code codeQuality: true, // https://getrector.com/find-rule?activeRectorSetGroup=core&rectorSet=core-code-quality codingStyle: true, // https://getrector.com/find-rule?activeRectorSetGroup=core&rectorSet=core-coding-style typeDeclarations: true, // https://getrector.com/find-rule?activeRectorSetGroup=core&rectorSet=core-type-declarations privatization: true, // https://getrector.com/find-rule?activeRectorSetGroup=core&rectorSet=core-privatization naming: true, // https://getrector.com/find-rule?activeRectorSetGroup=core&rectorSet=core-naming instanceOf: true, // https://getrector.com/find-rule?activeRectorSetGroup=core&rectorSet=core-instanceof earlyReturn: true, // https://getrector.com/find-rule?activeRectorSetGroup=core&rectorSet=core-early-return strictBooleans: true, // https://getrector.com/find-rule?activeRectorSetGroup=core&rectorSet=core-strict-booleans rectorPreset: true, symfonyCodeQuality: true, // https://getrector.com/find-rule?activeRectorSetGroup=symfony&rectorSet=symfony-code-quality symfonyConfigs: true, // https://getrector.com/find-rule?activeRectorSetGroup=symfony&rectorSet=symfony-configs ); ``` In the following example, you can see optimization thanks to the following features: - [Constructor parameter promoted as properties](https://www.php.net/manual/en/language.oop5.decon.php#language.oop5.decon.constructor.promotion) (available since PHP 8.0) - [`AsCommand` attribute to register a command](https://symfony.com/doc/7.3/console.html#console_registering-the-command) (available since Symfony 6.2) ``` +#[AsCommand(name: 'app:test', description: 'Command to test something.')] class TestCommand extends Command { - private Repository $repository; - - public function __construct(Repository $repository) + public function __construct(private readonly Repository $repository) { - $this->repository = $repository; - parent::__construct('app:test'); } - - protected function configure() - { - $this->setDescription('Command to test something.'); - } protected function execute(InputInterface $input, OutputInterface $output): int ``` #### Update JavaScript If you haven't renamed your Webpack file since 3.3, do it now as v5.0 no longer supports the old names. | Old name | New name | | --------------------------- | ------------------------------ | | ez.config.js | ibexa.config.js | | ez.config.manager.js | ibexa.config.manager.js | | ez.webpack.custom.config.js | ibexa.webpack.custom.config.js | `ibexa/rector` 5.0 also comes with the [JavaScript Transform module](https://github.com/ibexa/rector/blob/v5.0.0/js/README.md) to help you maintain your JavaScript code. Customize the `rector.config.js` config file by: - making it match your directory structure - modifying the list of enabled plugins and their configuration The example below is made to fix in place the JS files from `asset/js/` directory, and is ready to enable plugin rule sets one at a time (plugin path is relative to `vendor/ibexa/rector/` directory). ``` module.exports = { config: { paths: [ { input: 'assets/js', output: 'assets/js', }, ], }, plugins: (plugins) => { return [ './js/ibexa-rename-ez-global.js', //'./js/ibexa-rename-variables.js', //'./js/ibexa-rename-string-values.js', //'./js/ibexa-rename-trans-id.js', //'./js/ibexa-rename-in-translations.js', //'./js/ibexa-rename-icons.js', ]; }, pluginsConfig: (config) => { return config; }, }; ``` Install the tool dependencies once with the following command: ``` yarn --cwd ./vendor/ibexa/rector/js install ``` Run it using the following command: ``` yarn --cwd ./vendor/ibexa/rector/js transform ``` #### Update field type identifiers Several field type identifiers have changed. The old identifiers are still supported, but it's recommended to migrate as soon as possible. You can list existing field type services with the command `php bin/console debug:container --tag=ibexa.field_type`. The output as an `alias` column with new identifiers and a `legacy_alias` column with the old identifiers. Field type identifiers renaming map | old identifier (`legacy_alias`) | new identifier (`alias`) | | ------------------------------- | ------------------------------- | | ibexa_address | ibexa_address | | ezauthor | ibexa_author | | ezbinaryfile | ibexa_binaryfile | | ezboolean | ibexa_boolean | | ezcontentquery | ibexa_content_query | | ezcountry | ibexa_country | | ibexa_customer_group | ibexa_customer_group | | ezdate | ibexa_date | | ezdatetime | ibexa_datetime | | ezemail | ibexa_email | | ezfloat | ibexa_float | | ezform | ibexa_form | | ezgmaplocation | ibexa_gmap_location | | ezimage | ibexa_image | | ezimageasset | ibexa_image_asset | | ezinteger | ibexa_integer | | ezisbn | ibexa_isbn | | ezkeyword | ibexa_keyword | | ezlandingpage | ibexa_landing_page | | ezmatrix | ibexa_matrix | | ibexa_measurement | ibexa_measurement | | ezmedia | ibexa_media | | ezobjectrelation | ibexa_object_relation | | ezobjectrelationlist | ibexa_object_relation_list | | ibexa_product_specification | ibexa_product_specification | | ezrichtext | ibexa_richtext | | ezselection | ibexa_selection | | ibexa_seo | ibexa_seo | | ezstring | ibexa_string | | ibexa_taxonomy_entry | ibexa_taxonomy_entry | | ibexa_taxonomy_entry_assignment | ibexa_taxonomy_entry_assignment | | eztext | ibexa_text | | eztime | ibexa_time | | ezurl | ibexa_url | | ezuser | ibexa_user | You may have to update them in several places, for example: - Update the field identifiers in templates to display or edit fields or their definition. For example, in a `@IbexaCore/content_fields.html.twig` extension, `{% block ezstring_field %)` must be changed for `{% block ibexa_string_field %}` - Update the field identifiers in migration files #### Update icons The provided built-it icon set has been changed. The `ibexa/rector` JavaScript Transform module's plugin `ibexa-rename-icons.js` refactors the icon usage in JavaScript files. You may have to update them in other contexts, for example, in configuration files associating icons to content types or Page Builder blocks. The icon library file's path changed from `/bundles/ibexaicons/img/all-icons.svg` to `/bundles/ibexaadminuiassets/vendors/ids-assets/dist/img/all-icons.svg`. Some icons have been renamed. You can find an [`ibexa-rename-icons` map in `vendor/ibexa/rector/js/rules.config.json` (`"old-name": "new-name"`)](https://github.com/ibexa/rector/blob/v5.0.0/js/rules.config.json#L63). Icons renaming map | Old name | New name | | ----------------------- | ---------------------------- | | about-info | help | | about | info-square | | airtime | signal-radio | | align-center | align-text-center | | align-justify | align-text-justified | | align-left | align-text-left | | align-right | align-text-right | | approved | check-circle | | article | file-text | | assign-section | assign | | author | user-editor | | autosave-error | cloud-error | | autosave-off | cloud-discard | | autosave-on | cloud | | autosave-saved | cloud-check | | autosave-saving | cloud-synch | | b2b | handshake | | back | arrow-left | | back-current-date | calendar-back | | bestseller | badge-star | | block-invisible | block-hidden | | block-visible-recurring | block-lock | | blog | app-blog | | blog_post | note-blog | | bold | text-bold | | bookmark | favourite-outline | | bookmark-active | favourite-filled | | bookmark-manager | book | | box-collapse | arrow-move-right | | browse | folder-browse | | bubbles | message-bubble | | business-deal-cash | user-money | | button | cursor-clicked | | campaign | speaker | | captcha | form-captcha | | caret-back | arrow-chevron-left | | caret-double-back | arrow-double-left | | caret-double-next | arrow-double-right | | caret-down | arrow-chevron-down | | caret-expanded | arrow-double-left | | caret-next | arrow-chevron-right | | caret-up | arrow-chevron-up | | cart | shopping-cart | | cart-full | shopping-cart | | cart-upload | shopping-cart-arrow-up | | cart-wishlist | shopping-cart-heart | | category | tag | | checkbox | form-checkbox | | checkbox-multiple | form-check-list | | checkmark | form-check | | circle-caret-down | chevron-down-circle | | circle-caret-left | chevron-left-circle | | circle-caret-right | chevron-right-circle | | circle-caret-up | chevron-up-circle | | circle-close | discard-circle | | circle-create | add-circle | | circle-minus | minus-circle | | circle-pause | minus-circle | | clicked-recommendations | cursor-clicked-hand | | clipboard | clipboard-check | | collapse | arrow-collapse-right | | content-write | file-text-write | | column-settings | table-settings-column | | comment | message | | components | box-component | | connect | connection | | content-draft | draft | | contentlist | list-content | | content-list | list-content | | content-type | tools | | content-type-content | file-type | | content-type-group | tool-group | | copy-subtree | content-tree-copy | | create | add | | create-content | file-add | | create-location | content-tree-create-location | | customer | user-customer | | customer-portal | device-monitor-user | | customer-portal-page | app-user | | customer-type | device-monitor-type | | custom_tags | prompt | | date | calendar | | date-updated | calendar-reload | | discount-coupon | discount-ticket | | drafts | edit-draft | | dropdown | form-dropdown | | earth-access | world-cursor | | embed | text-embedded | | embed-inline | text-embedded-inline | | erp | connection-erp | | error | exclamation-mark | | error-icon | file-warning | | expand-left | arrow-expand-left | | expand-right | arrow-expand-right | | explore | ai | | fields | form-input | | file-video | video | | flash | lightning | | focus | arrows-outside | | focus-image | focus-target | | folder-empty | folder-open | | form | form-check-square | | full-view | arrows-full-view | | future-publication | calendar-clock | | gallery | image-gallery | | go-right | arrow-to-right | | go-to-root | content-tree-arrow-up | | go-up | arrow-to-up | | h1 | header-1 | | h2 | header-2 | | h3 | header-3 | | h4 | header-4 | | h5 | header-5 | | h6 | header-6 | | hide | visibility-hidden | | hierarchy | hierarchy-site-map | | history-file | file-history | | 'home-page' | home | | image-center | align-block-center | | image-editor | image-edit | | image-left | align-block-left | | image-right | align-block-right | | image-variations | image-focus | | imported-items | database-synch | | information | info-square | | input-hidden | form-input-hidden | | input-line | form-input-single-line | | input-line-multiple | form-input-multi-line | | input-number | form-input-number | | interface-block | forbidden | | italic | text-italic | | keyword | hash | | landing_page | layout-navbar | | landingpage-add | layout-navbar-add | | landingpage-preview | layout-navbar-visible | | languages | world | | languages-add | world-add | | last-purchased | cursor-clicked-hand | | last-viewed | app-recent | | layout-manager | layout | | link-content | file-link | | link-remove | unlink | | list | list-bullet | | list-numbered | list-number | | localize | target-location | | location-add-new | content-tree-create-location | | lock-unlock | unlock | | logout | log-out | | maform | chart-histogram | | mail | message-email | | mail-open | message-email-read | | markup | file-code | | menu | menu-hamburger | | move | folder-open-move | | newsletter | news | | notice | alert-error | | open-newtab | open-new-window | | open-sametab | open-same-window | | options | more | | order-history | file-history | | order-management | receipt-settings | | order-status | product-search | | panels | view-panels | | paragraph | text-paragraph | | paragraph-add | text-paragraph-add | | pdf-file | file-pdf | | personalize | user-target | | personalize-block | file-settings | | personalize-content | tag-settings | | pin-unpin | unpin | | place | pin-location | | places | pins-locations | | portfolio | suitcase | | previewed | overdue | | product-category | product-tag | | product-list | clipboard-list | | product_list | clipboard-list | | product-low | product-arrow-down | | product type | product-collection | | product-type | product-collection | | profile | user-profile | | publish | rocket | | publish-later | calendar-number | | publish-later-cancel | calendar-discard | | publish-later-create | calendar-add | | qa-content | qa-file | | qa-form | qa-form-check | | radio-button | form-radio | | radio-button-multiple | form-radio-list | | rate | stars | | rate-review | star-circle | | recent-activity | activity-clock | | recently-added | history | | recommendation-calls | arrows-circle | | redo | action-redo | | refresh | arrows-reload | | rejected | arrow-to-down-circle | | relations | hierarchy-square | | restore | arrow-restore | | restore-parent | content-tree-restore-parent | | review | message-edit | | roles | user-id | | rss | signal-rss | | schedule | calendar-schedule | | sections | database | | send-email | send | | settings-block | settings | | settings-config | settings-configure | | sites-all | sites | | spinner | arrow-rotate | | stats | chart-dots | | strikethrough | text-strikethrough | | subscriber | user-mail | | subscript | text-subscript | | superscript | text-superscript | | swap | arrows-synchronize | | system-information | info-circle | | trash-empty | trash-discard | | trash-notrashed | trash-open | | underscore | text-underline | | undo | action-undo | | un-focus | arrows-inside | | un-full-view | arrows-full-view-out | | upload-image | image-upload | | user-blocked | user-block | | user_group | user-group | | users-personalization | user-focus | | user-recycle | arrows-reload-user | | users-select | users-add | | user-tick | user-check | | version-compare | action-compare-versions | | version-compare-action | action-compare | | versions | archived-version | | vertical-left-right | arrow-collapse-expand | | view | visibility | | view-desktop | device-monitor | | view-hide | visibility-hidden | | view-mobile | device-mobile | | view-tablet | device-tablet | | warning | alert-warning | | warning-triangle | alert-warning | The following example illustrates the update of a custom page block's icon: ``` ibexa_fieldtype_page: blocks: event: name: About Block category: Custom - thumbnail: /bundles/ibexaicons/img/all-icons.svg#about + thumbnail: /bundles/ibexaadminuiassets/vendors/ids-assets/dist/img/all-icons.svg#info-square ``` ### Install new features' schemas Features which were optional 4.6 LTS Updates are now part of 5.0.0. - If you have already installed the feature, its schema has been updated by the previous step. - If you haven't installed the feature, you need to add its schema to your database. Store the SQL of the schema into a file, **review it carefully**, then run it. - If you mistakenly reinstall a schema, you might encounter "Table already exists" errors which can be ignored. #### Install AI actions schema **MySQL** ``` php bin/console ibexa:doctrine:schema:dump-sql vendor/ibexa/connector-ai/src/bundle/Resources/config/schema.yaml > schema_connector-ai.sql # Pause to review schema_connector-ai.sql mysql -u -p < schema_connector-ai.sql ``` **PostgreSQL** ``` php bin/console ibexa:doctrine:schema:dump-sql vendor/ibexa/connector-ai/src/bundle/Resources/config/schema.yaml > schema_connector-ai.sql # Pause to review schema_connector-ai.sql psql < schema_connector-ai.sql ``` #### Install date and time attribute type **MySQL** ``` php bin/console ibexa:doctrine:schema:dump-sql vendor/ibexa/product-catalog-date-time-attribute/src/bundle/Resources/config/schema.yaml > schema_date-time-attribute.sql # Pause to review schema_date-time-attribute.sql mysql -u -p < schema_date-time-attribute.sql ``` **PostgreSQL** ``` php bin/console ibexa:doctrine:schema:dump-sql vendor/ibexa/product-catalog-date-time-attribute/src/bundle/Resources/config/schema.yaml > schema_date-time-attribute.sql # Pause to review schema_date-time-attribute.sql psql < schema_date-time-attribute.sql ``` #### Install symbol attribute type **MySQL** ``` php bin/console ibexa:doctrine:schema:dump-sql vendor/ibexa/product-catalog-symbol-attribute/src/bundle/Resources/config/schema.yaml > schema_symbol-attribute.sql # Pause to review schema_symbol-attribute.sql mysql -u -p < schema_symbol-attribute.sql ``` **PostgreSQL** ``` php bin/console ibexa:doctrine:schema:dump-sql vendor/ibexa/product-catalog-symbol-attribute/src/bundle/Resources/config/schema.yaml > schema_symbol-attribute.sql # Pause to review schema_symbol-attribute.sql psql < schema_symbol-attribute.sql ``` #### Install collaboration **MySQL** ``` php bin/console ibexa:doctrine:schema:dump-sql vendor/ibexa/collaboration/src/bundle/Resources/config/schema.yaml > schema_collaboration.sql php bin/console ibexa:doctrine:schema:dump-sql vendor/ibexa/share/src/bundle/Resources/config/schema.yaml > schema_share.sql # Pause to review schema_collaboration.sql and schema_share.sql mysql -u -p < schema_collaboration.sql mysql -u -p < schema_share.sql ``` **PostgreSQL** ``` php bin/console ibexa:doctrine:schema:dump-sql vendor/ibexa/collaboration/src/bundle/Resources/config/schema.yaml > schema_collaboration.sql php bin/console ibexa:doctrine:schema:dump-sql vendor/ibexa/share/src/bundle/Resources/config/schema.yaml > schema_share.sql # Pause to review schema_collaboration.sql and schema_share.sql psql < schema_collaboration.sql psql < schema_share.sql ``` #### Install discounts (Commerce) **MySQL** ``` php bin/console ibexa:doctrine:schema:dump-sql vendor/ibexa/discounts/src/bundle/Resources/config/schema.yaml > schema_discounts.sql php bin/console ibexa:doctrine:schema:dump-sql vendor/ibexa/discounts-codes/src/bundle/Resources/config/schema.yaml > schema_discounts-codes.sql # Pause to review schema_discounts.sql and schema_discounts-codes.sql mysql -u -p < schema_discounts.sql mysql -u -p < schema_discounts-codes.sql ``` **PostgreSQL** ``` php bin/console ibexa:doctrine:schema:dump-sql vendor/ibexa/discounts/src/bundle/Resources/config/schema.yaml > schema_discounts.sql php bin/console ibexa:doctrine:schema:dump-sql vendor/ibexa/discounts-codes/src/bundle/Resources/config/schema.yaml > schema_discounts-codes.sql # Pause to review schema_discounts.sql and schema_discounts-codes.sql psql < schema_discounts.sql psql < schema_discounts-codes.sql ``` ### Clear cache pool The persistence cache pool needs to be cleared to be able to use the repository again. ``` php bin/console cache:pool:clear --all ``` ### Migrations #### Taxonomy ``` php bin/console ibexa:migrations:import vendor/ibexa/taxonomy/src/bundle/Resources/install/migrations/2025_08_09_14_47_mark_tag_as_container.yaml php bin/console ibexa:migrations:migrate --file=2025_08_09_14_47_mark_tag_as_container.yaml --siteaccess=admin ``` #### Product catalog ``` php bin/console ibexa:migrations:import vendor/ibexa/product-catalog/src/bundle/Resources/migrations/2025_07_09_13_52_mark_product_category_container.yaml php bin/console ibexa:migrations:migrate --file=2025_07_09_13_52_mark_product_category_container.yaml --siteaccess=admin ``` #### Corporate accounts (Experience) (Commerce) ``` php bin/console ibexa:migrations:import vendor/ibexa/corporate-account/src/bundle/Resources/migrations/2025_07_08_09_27_set_container_to_company.yaml php bin/console ibexa:migrations:migrate --file=2025_07_08_09_27_set_container_to_company.yaml --siteaccess=admin ``` ### Generate GraphQL schema GraphQL usage is no longer required for the Ibexa DXP back office. If you are using GraphQL in your project, you can generate its schema by running: ``` php bin/console ibexa:graphql:generate-schema ``` ### Upgrade GraphQL usage - In 4.6, pagination for [RelationList field type](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/relationlistfield/index.md) is disabled by default, and can be enabled using the `ibexa.graphql.schema.ibexa_object_relation_list.enable_pagination` parameter - In 5.0, pagination for RelationList field type is always activated and can't be disabled. The previous parameter doesn't exist anymore and is ignored if set If you have code based on `relations` request returning the entire list, you have to update it. For more information, see [Pagination in GraphQL](https://doc.ibexa.co/en/latest/api/graphql/graphql_queries/#pagination). ### Update search indexes Ensure your search index is up to date with the following command: ``` php bin/console ibexa:reindex ``` ### Finalizing #### Clear cache and rebuild Finish the update process: ``` composer run-script post-update-cmd ``` #### HTTP Cache Use the newer VCL files. Depending on your reverse proxy, you'll find them in the following directories: - Varnish: `vendor/ibexa/http-cache/docs/varnish/vcl/` - Fastly: `vendor/ibexa/fastly/fastly/` #### Ibexa Cloud Generate the Ibexa Cloud Platform.sh configuration files, review the changes with your own version, and merge your customizations. ``` composer ibexa:setup --platformsh ``` #### Conclusion Your project is now running the latest major version of Ibexa DXP. To reach the last patch version, see [Update from v5.0.x to v5.0.latest](https://doc.ibexa.co/en/latest/update_and_migration/from_5.0/update_from_5.0/index.md) # Update from v5.0.x to v5.0.latest To update from v4.6.x, see [Update from v4.6 to v5.0](https://doc.ibexa.co/en/latest/update_and_migration/from_4.6/update_to_5.0/index.md). To update from an older version, visit [the update page](https://doc.ibexa.co/en/latest/update_and_migration/update_ibexa_dxp/index.md) and choose the applicable path. ## Update the application Note which version you actually have before starting. First, run: **Ibexa Headless** ``` yarn upgrade @ibexa/frontend-config @ibexa/ts-config composer require ibexa/headless:5.0.7 --with-all-dependencies --no-scripts composer recipes:install ibexa/headless --force -v ``` **Ibexa Experience** ``` yarn upgrade @ibexa/frontend-config @ibexa/ts-config composer require ibexa/experience:5.0.7 --with-all-dependencies --no-scripts composer recipes:install ibexa/experience --force -v ``` **Ibexa Commerce** ``` yarn upgrade @ibexa/frontend-config @ibexa/ts-config composer require ibexa/commerce:5.0.7 --with-all-dependencies --no-scripts composer recipes:install ibexa/commerce --force -v ``` Then execute the instructions below starting from the version you're upgrading from. ## v5.0.1 Some packages increase their type hinting strictness. You can run [Ibexa DXP Rector](https://github.com/ibexa/rector/blob/v5.0.1/README.md) to update your code. ## v5.0.2 ### Database update **MySQL** ``` CREATE TABLE ibexa_messenger_messages ( id BIGINT AUTO_INCREMENT NOT NULL, body LONGTEXT NOT NULL, headers LONGTEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at DATETIME NOT NULL COMMENT '(DC2Type:datetime_immutable)', available_at DATETIME NOT NULL COMMENT '(DC2Type:datetime_immutable)', delivered_at DATETIME DEFAULT NULL COMMENT '(DC2Type:datetime_immutable)', INDEX ibexa_messenger_created_at_idx (created_at), INDEX ibexa_messenger_available_at_idx (available_at), INDEX ibexa_messenger_delivered_at_idx (delivered_at), PRIMARY KEY(id) ) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_520_ci` ENGINE = InnoDB; CREATE TABLE ibexa_messenger_lock_keys ( key_id VARCHAR(64) NOT NULL, key_token VARCHAR(44) NOT NULL, key_expiration INT UNSIGNED NOT NULL, PRIMARY KEY(key_id) ) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_520_ci` ENGINE = InnoDB; ``` **PostgreSQL** ``` CREATE TABLE ibexa_messenger_messages ( id BIGSERIAL NOT NULL, body TEXT NOT NULL, headers TEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, available_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, delivered_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY(id) ); CREATE INDEX ibexa_messenger_created_at_idx ON ibexa_messenger_messages (created_at); CREATE INDEX ibexa_messenger_available_at_idx ON ibexa_messenger_messages (available_at); CREATE INDEX ibexa_messenger_delivered_at_idx ON ibexa_messenger_messages (delivered_at); COMMENT ON COLUMN ibexa_messenger_messages.created_at IS '(DC2Type:datetime_immutable)'; COMMENT ON COLUMN ibexa_messenger_messages.available_at IS '(DC2Type:datetime_immutable)'; COMMENT ON COLUMN ibexa_messenger_messages.delivered_at IS '(DC2Type:datetime_immutable)'; CREATE TABLE ibexa_messenger_lock_keys ( key_id VARCHAR(64) NOT NULL, key_token VARCHAR(44) NOT NULL, key_expiration INT NOT NULL, PRIMARY KEY(key_id) ); ``` On Commerce, run this additional update queries: **MySQL** ``` ALTER TABLE ibexa_discount ADD indexed_at DATETIME DEFAULT NULL COMMENT '(DC2Type:datetime_immutable)'; CREATE INDEX ibexa_discount_indexed_at_idx ON ibexa_discount (indexed_at); ``` **PostgreSQL** ``` ALTER TABLE ibexa_discount ADD indexed_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL; COMMENT ON COLUMN ibexa_discount.indexed_at IS '(DC2Type:datetime_immutable)'; CREATE INDEX ibexa_discount_indexed_at_idx ON ibexa_discount (indexed_at); ``` ## v5.0.3 ### Form Builder performance fix: missing indexes on form submission data (Experience) (Commerce) In large production databases, the `ibexa_form_submission` and `ibexa_form_submission_data` tables may contain a lot of rows. Missing indexes can cause high CPU load and slow queries. Run the provided SQL upgrade script to add the missing indexes to your database: **MySQL** ``` mysql -u -p < vendor/ibexa/installer/upgrade/db/mysql/ibexa-5.0.2-to-5.0.3.sql ``` **PostgreSQL** ``` psql < vendor/ibexa/installer/upgrade/db/postgresql/ibexa-5.0.2-to-5.0.3.sql ``` ## v5.0.4 ### Database update (Experience) (Commerce) From a platform first installed on v5.0.3 or updated precisely to v5.0.3, you need to execute the requests below. If the platform comes from lower than v5.0.3 and is updated to higher than v5.0.3, you don't need this part (but if you run the requests anyway, you only obtain error messages, nothing being broken or lost). **MySQL** ``` ALTER TABLE `ibexa_site_public_access` ADD COLUMN `tree_root_location_id` INT DEFAULT NULL; ALTER TABLE `ibexa_site_public_access` ADD INDEX `ibexa_spa_trl_id` (`tree_root_location_id`); UPDATE ibexa_site_public_access SET tree_root_location_id = CAST(JSON_UNQUOTE(JSON_EXTRACT(config, '$."ibexa.site_access.config.content.tree_root.location_id"')) AS SIGNED) WHERE tree_root_location_id IS NULL AND JSON_EXTRACT(config, '$."ibexa.site_access.config.content.tree_root.location_id"') IS NOT NULL; ``` **PostgreSQL** ``` ALTER TABLE ibexa_site_public_access ADD COLUMN tree_root_location_id INT DEFAULT NULL; CREATE INDEX "ibexa_spa_trl_id" ON "ibexa_site_public_access" ("tree_root_location_id"); UPDATE ibexa_site_public_access SET tree_root_location_id = (config::jsonb ->> 'ibexa.site_access.config.content.tree_root.location_id')::integer WHERE tree_root_location_id IS NULL AND config::jsonb ? 'ibexa.site_access.config.content.tree_root.location_id'; ``` ## v5.0.5 ### Elasticsearch 8 support As of v5.0.5, Ibexa DXP adds support for Elasticsearch 8.19 or higher. You can continue using [unsupported Elasticsearch 7.16.2+](https://www.elastic.co/support/eol), but it's recommended to upgrade to Elasticsearch 8 for improved performance and security features. When choosing to keep using Elasticsearch 7.16.2, adjust your configuration as described in the [Update configuration](#update-configuration) section below to avoid using deprecated settings. If you choose to upgrade to Elasticsearch 8, follow these steps: #### Update Elasticsearch server Upgrade your Elasticsearch server to version 8.19 or higher. For detailed instructions, follow the [Elasticsearch upgrade guide](https://www.elastic.co/guide/en/elastic-stack/8.19/upgrading-elastic-stack.html#prepare-to-upgrade). When you use Ibexa Cloud, see [Elasticsearch service](https://docs.upsun.com/add-services/elasticsearch.html) for a list of supported versions. #### Update configuration Update your configuration in `config/packages/ibexa_elasticsearch.yaml`. ##### Replace deprecated connection pool settings The deprecated `connection_pool` and `connection_selector` settings are now ignored and don't have any effect. Replace them with appropriate `node_pool_selector` and `node_pool_resurrect` settings: ``` # Old configuration (Elasticsearch 7 - deprecated) ibexa_elasticsearch: connections: default: connection_pool: 'Elasticsearch\ConnectionPool\StaticNoPingConnectionPool' connection_selector: 'Elasticsearch\ConnectionPool\Selectors\RoundRobinSelector' ``` ``` # New configuration (Elasticsearch 7 and 8) ibexa_elasticsearch: connections: default: node_pool_selector: 'Elastic\Transport\NodePool\Selector\RoundRobin' node_pool_resurrect: 'Elastic\Transport\NodePool\Resurrect\NoResurrect' ``` For more information, see [Node pool settings](https://doc.ibexa.co/en/latest/search/search_engines/elasticsearch/configure_elasticsearch/#node-pool-settings). ##### Remove trace option The `trace` debugging option is no longer available. ``` # Old configuration (Elasticsearch 7) ibexa_elasticsearch: connections: default: debug: true trace: true ``` ``` # New configuration (Elasticsearch 7 and 8) ibexa_elasticsearch: connections: default: debug: true # Trace option is no longer available ``` #### Reindex content After upgrading to Elasticsearch 8 and updating your configuration, reindex the search engine: 1. Push the index templates: ``` php bin/console ibexa:elasticsearch:put-index-template --overwrite ``` 1. Reindex your content: ``` php bin/console ibexa:reindex ``` ### Database update Run the provided SQL upgrade script to ensure the Messenger tables for [background tasks](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/background_tasks/index.md) exist in your database: **MySQL** ``` mysql -u -p < vendor/ibexa/installer/upgrade/db/mysql/ibexa-5.0.4-to-5.0.5.sql ``` **PostgreSQL** ``` psql < vendor/ibexa/installer/upgrade/db/postgresql/ibexa-5.0.4-to-5.0.5.sql ``` ## v5.0.5 No additional steps needed. ## v5.0.6 ### Database update (Experience) (Commerce) Run the provided SQL upgrade script to adapt your database to latest change in [form builder](https://doc.ibexa.co/en/latest/content_management/forms/form_builder_guide/index.md)'s `max_length` validator behavior: **MySQL** ``` mysql -u -p < vendor/ibexa/installer/upgrade/db/mysql/ibexa-5.0.5-to-5.0.6.sql ``` **PostgreSQL** ``` psql < vendor/ibexa/installer/upgrade/db/postgresql/ibexa-5.0.5-to-5.0.6.sql ``` Prior, `0` was interpreted as "no length limit". Now, `0` is interpreted as "length limited to zero characters" and `NULL` as "no length limit". ### Ibexa Cloud configuration update If you're using Ibexa Cloud, you must install a new package and update your cloud configuration. First, install the `ibexa/cloud` package: ``` composer require ibexa/cloud ``` Then, update your cloud configuration. Instead of the old `composer ibexa:setup --platformsh` command, use: ``` php bin/console ibexa:cloud:setup --upsun ``` This command generates or updates the cloud configuration files. Additionally, you must remove the following line from your `.platform.app.yaml` file if it exists: ``` curl -fs https://get.symfony.com/cloud/configurator | bash ``` ## v5.0.7 ### Update Symfony from 7.3 to 7.4 This version of Ibexa DXP requires [Symfony 7.4](https://symfony.com/releases/7.4). Update Symfony constraints in `composer.json` before updating the packages. 1. In `composer.json`, update `extra.symfony.require` to allow installing a higher Symfony version: ``` "extra": { "symfony": { "require": "7.4.*" } } ``` 1. To allow installing Symfony 7.4, update the requirements for **all** `symfony` packages in `composer.json` as in the example below: ``` - "symfony/"": "7.3.*", + "symfony/"": "7.4.*", ``` 1. Review your code, configuration, and third-party bundles for Symfony 7.4 compatibility. For more details about the new version, see the official Symfony [upgrade instructions](https://github.com/symfony/symfony/blob/7.4/UPGRADE-7.4.md) and [blog posts introducing this release](https://symfony.com/blog/category/living-on-the-edge/8.0-7.4). Key changes include: - Array-based PHP configuration format As part of the [array-based PHP configuration format](https://symfony.com/blog/new-in-symfony-7-4-better-php-configuration), a `config/reference.php` file will be created. You should commit this file to the repository. - Independent application cache directory Symfony 7.4 introduces a new [share directory](https://symfony.com/blog/new-in-symfony-7-4-share-directory), dedicated for storing application cache on the file system. If you decide to configure it (for example, by setting the `APP_SHARE_DIR` environment variable), review your existing scripts for explicit `var/cache` usage (for example, `rm -rf var/cache`) and decide whether to include `var/share` in the script. 1. Update Ibexa packages by running: **Ibexa Headless** ``` yarn upgrade @ibexa/frontend-config @ibexa/ts-config composer require ibexa/headless:v5.0.7 --with-all-dependencies --no-scripts composer recipes:install ibexa/headless --force -v ``` **Ibexa Experience** ``` yarn upgrade @ibexa/frontend-config @ibexa/ts-config composer require ibexa/experience:v5.0.7 --with-all-dependencies --no-scripts composer recipes:install ibexa/experience --force -v ``` **Ibexa Commerce** ``` yarn upgrade @ibexa/frontend-config @ibexa/ts-config composer require ibexa/commerce:v5.0.7 --with-all-dependencies --no-scripts composer recipes:install ibexa/commerce --force -v ``` 1. Manually restore the entry for `JMSTranslationBundle` in `config/bundles.php` to [its previous position](https://github.com/ibexa/commerce-skeleton/blob/v5.0.6/config/bundles.php#L14): ``` FOS\HttpCacheBundle\FOSHttpCacheBundle::class => ['all' => true], JMS\TranslationBundle\JMSTranslationBundle::class => ['all' => true], Liip\ImagineBundle\LiipImagineBundle::class => ['all' => true], ``` You're now running [Symfony 7.4, the current long-term support version](https://symfony.com/releases/7.4). ### Ibexa Cloud `ibexa:setup` command deprecation Following the changes introduced in v5.0.6, the `ibexa:setup` command is deprecated as of v5.0.7 and will be removed in v6.0.0. Additionally, the `ibexa/cloud` package must be installed for the Ibexa Cloud build to succeed. The command `ibexa:cloud:setup` from this package replaces the deprecated `ibexa:setup`. To learn how to adjust your configuration, see [update instructions for v5.0.6](#ibexa-cloud-configuration-update). ### Database update (Experience) (Commerce) Run the provided SQL upgrade script to update your database: **MySQL** ``` mysql -u -p < vendor/ibexa/installer/upgrade/db/mysql/ibexa-5.0.6-to-5.0.7.sql ``` **PostgreSQL** ``` psql < vendor/ibexa/installer/upgrade/db/postgresql/ibexa-5.0.6-to-5.0.7.sql ``` ## LTS Updates and additional packages [LTS Updates](https://doc.ibexa.co/en/latest/ibexa_products/editions/#lts-updates) are standalone packages with their own update procedures. To use the [latest features](https://doc.ibexa.co/en/latest/release_notes/ibexa_dxp_v5.0/index.md) added to them, update them separately with the following commands: **Integrated help** ### Integrated help (LTS Update) See [Integrated help](https://doc.ibexa.co/en/latest/administration/back_office/integrated_help/index.md) for more information. If you're already using it, run the following command to get the latest version of this feature: ``` composer require ibexa/integrated-help:5.0.7 ``` **Anthropic connector** ### Anthropic connector (LTS Update) See [how to configure Anthropic connector](https://doc.ibexa.co/en/latest/ai_actions/configure_ai_actions/#install-anthropic-connector) for more information. If you're already using it, run the following command to get the latest version of this feature: ``` composer require ibexa/connector-anthropic:5.0.7 ``` **Real-time collaborative editing** ### Real-time collaborative editing To learn more about the [Real-time editing](https://doc.ibexa.co/en/latest/content_management/collaborative_editing/collaborative_editing_guide/index.md), see the [installation and configuration instructions](https://doc.ibexa.co/en/latest/content_management/collaborative_editing/configure_collaborative_editing/index.md). If you're already using it, run the following command to get the latest version of this feature: ``` composer require ibexa/fieldtype-richtext-rte:5.0.7 ibexa/ckeditor-premium:5.0.7 ``` **Shopping list** ### Shopping list (LTS Update) (Commerce) To learn more about the [Shopping list](https://doc.ibexa.co/en/latest/commerce/shopping_list/shopping_list_guide/index.md), see the [installation and configuration instructions](https://doc.ibexa.co/en/latest/commerce/shopping_list/install_shopping_list/index.md). # Migrating from eZ Publish Platform eZ Publish Platform (5.x) was a transitional version of the Ibexa CMS, bridging the gap between the earlier generation called eZ Publish (sometimes referred to as *legacy*), and eZ Platform, the predecessor to Ibexa DXP. eZ Publish Platform introduced a new Symfony-based technology stack that could be run along the old (*legacy*) one. This bridging is still possible using something called Legacy Bridge, an optional package for eZ Platform. This fluid change allows eZ Publish users to migrate to eZ Platform gradually, using the bridging as an intermediary stepping stone. ## Upgrade process An upgrade from eZ Publish Platform 5.4.x (Enterprise edition) or 2014.11 (Community edition) to newer versions of eZ Platform must be performed in stages. You can upgrade from eZ Publish Platform directly to the v1.7 LTS release. You can then proceed with consecutive upgrades to further versions: v1.13 LTS and 2.x. > **Caution: Things to be aware of when planning a migration** > > 1. While the instructions below are fully supported, we are aware that the community, partners and customers come from a wide range of different versions of eZ Publish, some with issues that don't surface before attempting a migration. That's why we and the community are actively gathering feedback on Slack and/or support channels for Enterprise customers to gradually improve the migration scripts and instructions. Reach out before you start so others who have done this before you can support you. > > 1. As of eZ Platform v1.11, Legacy Bridge is a supported option for 1.x and future 2.x series. This means you can plan for a more gradual migration if you want, just like you could on eZ Publish Platform 5.x, with a more feature-rich version of eZ Platform and (with 2.x) also more recent version of Symfony. This is a great option for those who want the latest features and are comfortable with more frequent releases. > > 1. Additionally there are some other topics to be aware of for the code migration from eZ Publish to eZ Platform: > > - Symfony deprecations. The recommended version to migrate to is eZ Platform v2.5 LTS, which is using Symfony 3.4 LTS. > - [Field types reference](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/field_type_reference/index.md) for overview of field types that do and don't exist in eZ Platform > - API changes. While we have a strict backwards compatibility focus, some deprecated API features were removed and some changes were done to internal parts of the system. See [ezpublish-kernel:doc/bc/changes-6.0.md](https://github.com/ezsystems/ezpublish-kernel/blob/v6.7.0/doc/bc/changes-6.0.md) > **Note: Note** > > If you're migrating from a legacy eZ Publish version, this page contains the information you need. However, first have a look at an overview of the process in [Migrating from eZ Publish](https://doc.ibexa.co/en/latest/update_and_migration/migrate_to_ibexa_dxp/migrating_from_ez_publish/index.md). This section describes how to upgrade your existing  eZ Publish Platform  5.4/2014.11 installation to eZ Platform and eZ Enterprise. Make sure that you have a working [backup](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/backup/index.md) of the site before you do the actual upgrade, and that the installation you're performing the upgrade on is offline. ### Note on Paths - `/`: The root directory where the 5.4/2014.11 installation is located in, for example: `/home/myuser/old_www/` or `/var/sites/ezp/`. - `/`: The root directory where the installation is located in, for example: `/home/myuser/new_www/` or `/var/sites/[ezplatform|ezplatform-ee]/`. ## Check for requirements - Information regarding system requirements can be found on the [Requirements documentation page](https://doc.ibexa.co/en/latest/getting_started/requirements/index.md); notable changes include: - PHP 7.1 or higher - MariaDB or MySQL 5.5 or higher *(Postgres possible for upgrades, but not yet supported by the installer for new installations)* - Browser from 2017 or newer for use with eZ Platform backend UI - This page assumes you have composer installed on the machine and that it's a recent version. > **Tip: Clearing cache** > > If at any point during the migration procedure you encounter problems with the cache, refer to [How to clear the cache properly?](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/support_and_maintenance_faq/#how-to-clear-the-cache-properly). ## Step 1: Extract eZ Platform/Enterprise v1.7 The easiest way to upgrade the distribution files is to extract a clean installation of eZ Platform / eZ Enterprise to a separate directory. ## Step 2: Move over code and config ### 2.1. Code If you have code in src folder, move that over: `/src => /src` ### 2.2. Composer #### 2.2.1 Move over own packages Assuming you have own composer packages *(libraries and bundles, but not eZ Publish legacy packages)*, execute commands like below to add them to new install in ``: `composer require --no-update "vendor/package:~1.3.0"` Adapt the command with your `vendor`, `package`, version number, and add `"–dev"` if a given package is for dev use. Also check if there are other changes in `composer.json` you should move over. #### 2.2.2 Install XmlText field type While no longer bundled, the XmlText field type still exists and is needed to perform a migration from eZ Publish's XmlText to the new docbook-based format used by the RichText field type. If you plan to use Legacy Bridge for a while before migrating content, you also need this for rendering content with XMLText. From `` execute: `composer require --no-update "ezsystems/ezplatform-xmltext-fieldtype:^1.3.0"` > **Note: Note** > > As of v1.3, be aware this field type now uses the Content View system introduced in eZ Platform 1.0, so make sure you adapt custom templates and override rules if you plan to use this for rendering content (in Legacy Bridge setup). ### 2.3. Config To move over your own custom configurations, follow the conventions below and manually move the settings over: - `/ezpublish/config/parameters.yaml => /app/config/parameters.yaml` - *For parameters like before, for new parameters you'll be prompted on later step.* - `/ezpublish/config/config.yaml => /app/config/config.yaml` - *For system/framework config, and for defining global db, cache, search settings.* - `/ezpublish/config/ezpublish.yaml => /app/config/ezplatform.yaml` - *For SiteAccess, site groups and repository settings.* > **Note: Changes to repository configuration** > > When moving configuration over, be aware that as of 5.4.5 and higher, repository configuration has been enhanced to allow configuring storage engine and search engine independently. > > ``` > # Default ezplatform.yaml Repositories configuration with comments > ezplatform: > # Repositories configuration, set up default Repository to support solr if enabled > repositories: > default: > # For storage engine use kernel default (current LegacyStorageEngine) > storage: ~ > # For search engine, pick the one configured in parameters.yaml, either "legacy" or "solr" > search: > engine: '%search_engine%' > connection: default > ``` > **Note: Make sure to adapt SiteAccess names** > > In the default configurations in **ezplatform.yaml** you can find existing SiteAccesses like `site`, and depending on installation perhaps a few others, all under a site group called `site\_group`. Make sure to change those to what you had in **ezpublish.yaml** to avoid issues with having to log in to your website, given `user/login` policy rules need to be updated if you change names of SiteAccess as part of the upgrade. #### 2.3.1 Image aliases Image aliases defined in legacy must also be defined for eZ Platform. Since image aliases in legacy may be scattered around in different `image.ini` files in various extensions, you may find it easier to find all image alias definitions using the legacy admin (**Setup** > **Ini settings**). See [Image documentation page](https://doc.ibexa.co/en/latest/content_management/images/images/index.md) for information about how to define image aliases. For an example, see a legacy image alias defined as follows in `ezpublish_legacy/settings/siteaccess/ezdemo_site/image.ini.append.php`: ``` [articleimage] Reference= Filters[] Filters[]=geometry/scalewidth=770 [articlethumbnail] Reference= Filters[] Filters[]=geometry/scaledownonly=170;220 ``` The corresponding image alias configuration for eZ Platform would be: ``` ezpublish: siteaccess: groups: # Define the siteaccesses where given image aliases are in use image_aliases_group: [ezdemo_site, eng, ezdemo_site_admin, admin] system: image_aliases_group: image_variations: articleimage: reference: null filters: - { name: geometry/scalewidth, params: [770] } articlethumbnail: reference: null filters: - { name: geometry/scaledownonly, params: [170, 220] } ``` ### 2.4. Bundles Move over registration of *your* bundles you have from src and from composer packages, from old to new kernel: `/ezpublish/EzPublishKernel.php => /app/AppKernel.php` ### 2.5. Optional: Install Legacy Bridge If you don't plan to migrate content directly to newer eZ Platform field types, you can optionally install Legacy Bridge and gradually handle code and subsequent content migration afterwards. For installation instructions see [here](https://github.com/ezsystems/LegacyBridge/blob/master/INSTALL.md). > **Note: Note** > > The Legacy Bridge integration doesn't have the same performance, scalability or integrated experience as a pure Platform setup. Like on eZ Publish Platform 5.x there are known edge cases where, for instance, cache or search index cannot always be immediately updated across the two systems using the bridge. This is one of the many reasons why we recommend a pure Platform setup where that is possible. #### 2.5.1 Set up symlinks for legacy folders As eZ Publish legacy is installed via composer, we need to take care of placing some files outside its generated `/ezpublish_legacy/` folder, and for instance use symlink to place them inside during installation. 1. For design and settings files that you typically version in git, you can now take advantage of Legacy Bridge's built-in symlink convention. So as installation already hinted about, you can generate a structure and set up symlinks by using `bin/console ezpublish:legacy:symlink -c`. This creates folders you can use below in `/src/legacy_files/`. 2. The same goes for the `/ezpublish_legacy/var/[/]storage` folder. However, as it's typically not versioned in git, there's no predefined convention for this. If you create a folder within your project structure for symlinking into this folder, as opposed to a mount somewhere else, make sure to mark this folder as ignored by git once it and the corresponding `.keep` file have been added to your checkout. The example below assumes `/src/legacy_files/storage` was created for this purpose, if you opt for something else just adjust the instructions. #### 2.5.2 Upgrade the legacy distribution files The easiest way to upgrade the distribution files is to copy the directories that contain site-specific files from the existing 5.4 installation into `//ezpublish_legacy`. Make sure you copy the following directories: - `/ezpublish_legacy/design/` => `/src/legacy_files/design/` - *Don't include built-in designs: admin, base, standard or admin2* - `/ezpublish_legacy/settings/siteaccess/` => `/src/legacy_files/settings/siteaccess/` - `/ezpublish_legacy/settings/override/*` => `/src/legacy_files/settings/override/*` - Other folders to move over *(or potentially set up symlinks for)* if applicable: - ezpublish_legacy/var/storage/packages - ezpublish_legacy/extension/\* *(don't include the built-in / composer provided ones, like: ezflow, ezjscore, ezoe, ezodf, ezie, ezmultiupload, ezmbpaex, ez_network, ezprestapiprovider, ezscriptmonitor, ezsi, ezfind)* - ezpublish_legacy/config.php and ezpublish_legacy/config.cluster.php > **Note: Note** > > Since writable directories and files have been replaced / copied, their permissions might have changed. You most likely need to reconfigure webserver user permissions as instructed further down. ### 2.6 Binary files Binary files can simply be copied from the old to the new installation: `/web/var[/]/storage => /web/var[/]/storage` > **Note: Note** > > In the eZ Publish Platform 5.x installation `web/var` is a symlink to `ezpublish_legacy/var`, so if you can't find it in path above you can instead copy the storage files to the similar `ezpublish_legacy/var[/]/storage` path. ### 2.7 Re-apply permissions and update composer Since writable directories and files have been replaced / copied, their permissions might have changed. You need to re-apply them. When that is done, execute the following to update and install all packages from within ``: `composer update --prefer-dist` > **Note: Note** > > At the end of the process, you're asked for values for parameters.yaml not already moved from old installation, or new *(as defined in parameters.yaml.dist)*. ### 2.8 Register EzSystemsEzPlatformXmlTextFieldTypeBundle Add the following new bundle to your new kernel file, `/app/AppKernel.php`: `new EzSystems\EzPlatformXmlTextFieldTypeBundle\EzSystemsEzPlatformXmlTextFieldTypeBundle(),` ## Step 3: Upgrade the database ### 3.1. Execute update SQL Import to your database the changes provided in one of the following files. It's also recommended to read inline comments as you might not need to run some of the queries: `MySQL: /vendor/ezsystems/ezpublish-kernel/data/update/mysql/dbupdate-5.4.0-to-6.13.0.sql` `Postgres: /vendor/ezsystems/ezpublish-kernel/data/update/postgres/dbupdate-5.4.0-to-6.13.0.sql` ### 3.2. Once you're ready to migrate content to Platform field types Steps here should only be done once you're ready to move away from legacy and Legacy Bridge, as the following field types aren't supported by legacy. In other words, content you have migrated isn't be editable in legacy admin interface anymore, but rather in the more modern eZ Platform back-end UI, allowing you to take full advantage of what the Platform has to offer. The field types unsupported in eZ Platform are: - [XmlText](#321-migrate-xmltext-content-to-richtext) - [Page](#322-migrate-page-field-to-page-ez-enterprise-only) - Star Rating For field types which don't have specific procedures below, you must take one of the following actions: - implement them yourself in eZ Platform - remove them from all content types that use them > **Tip: Tip** > > To find out which content types use a specific field type, you can run the following SQL query on your database (in this case, for the Star Rating field type): > > ``` > select contentclass_id from ezcontentclass_attribute where data_type_string='ezsrrating' group by contentclass_id; > ``` #### 3.2.1 Migrate XmlText content to RichText You should test the XmlText to RichText conversion before you apply it to a production database. RichText has a stricter validation compared to XmlText and you may have to fix some of your XmlText before you're able to convert it to RichText. Run the conversion script on a copy of your production database as the script is rather resource-intensive. `php -d memory_limit=1536M bin/console ezxmltext:convert-to-richtext --dry-run --export-dir=ezxmltext-export --export-dir-filter=notice,warning,error --concurrency 4 -v` - `-d memory_limit=1536M` specifies that each conversion process gets 1536MB of memory. This should be more than sufficient for most databases. If you have small `ezxmltext` documents, you may decrease the limit. If you have huge `ezxmltext` documents, you may need to increase it. See PHP documentation for more information about the [memory_limit setting](https://www.php.net/manual/en/ini.core.php#ini.memory-limit). - `--dry-run` prevents the conversion script from writing anything back to the database. It just tests if it's able to convert all the `ezxmltext` documents. - `--export-dir` specifies a directory where it dumps the `ezxmltext` for content object attributes which the conversion script finds problems with - `--export-dir-filter` specifies what severity the problems found needs to be before the script dumps the `ezxmltext`: - `notice`: `ezxmltext` contains problems which the conversion tool was able to fix automatically and likely don't need manual intervention - `warning`: the conversion tool was able to convert the `ezxmltext` to valid `richtext`, but data could have been altered/removed/added in the process. Manual supervision recommended - `error`: the `ezxmltext` text cannot be converted and manual changes are required. - `--concurrency 4` specifies that the conversion script spawns four child processes which run the conversion. If you have dedicated hardware for running the conversion, you should use concurrency level that matches the number of logical CPUs on your system. If your system needs to do other tasks while running the conversion, you should run with a smaller number. - `-v` specifies verbosity level. You may increase the verbosity level by supplying `-vv`, but `-v` is sufficient in most cases. The script also has an `--image-content-types` option which you should use if you have custom image classes. With this option, you specify the content class identifiers: `php bin/console ezxmltext:convert-to-richtext --image-content-types=image,custom_image -v` The script needs to know these identifiers to convert `` tags correctly. Failing to do so prevents the editor from showing image thumbnails of embedded image objects. You may find the image content types in your installation by looking for these settings in `content.ini(.append.php)`: ``` [RelationGroupSettings] ImagesClassList[] ImagesClassList[]=image ``` If the `--image-content-types` option isn't specified, the default setting `image` is used. > **Note: Note** > > Version of the migration script in ezplatform-xmltext-fieldtype prior to v1.6.0 would fail to convert embedded images correctly. If you have a database which you have already converted with an old version, you may rerun the `convert-to-richtext` command with the following options: > > `php bin/console ezxmltext:convert-to-richtext --fix-embedded-images-only -v` > > The use of `--image-content-types` is also supported together with `--fix-embedded-images-only`. Use it to specify custom image content types. > **Note: Note** > > There is no corresponding `ImagesClassList[]` setting in eZ Platform. So even though you have customer image classes, you don't need to configure this in the eZ Platform configuration when migrating. If you later realize that you provided the convert script with incorrect image content type identifiers, it's perfectly safe to re-execute the command as long as you use the `--fix-embedded-images-only`. So, if you first ran the command: `php bin/console ezxmltext:convert-to-richtext --image-content-types=image,custom_image -v` But later realize the last identifier should be `profile`, not `custom_image`, you may execute : `php bin/console ezxmltext:convert-to-richtext --image-content-types=image,profile -v` The last command would then ensure embedded objects with content type identifier `custom_image` are no longer tagged as images, while embedded objects with content type identifier `profile` are. Using the option `--export-dir`, the conversion exports problematic `ezxmltext` to files with the name pattern `[export-dir]/ezxmltext_[contentobject_id]_[contentobject_attribute_id]_[version]_[language].xml`. A corresponding `.log` file is also created which includes information about why the conversion failed. Be aware that the reported location of the problem may not be accurate or may be misleading. Below is an example of a xml dump, `ezxmltext_12_1234_2_eng-GB.xml`: ```
    col1 col2 col3 col4
    ``` The corresponding log file, `ezxmltext_12_1234_2_eng-GB.log`: ``` notice: Found ez-temporary attribute in a ezxmltext paragraphs. Removing such attribute where contentobject_attribute.id=1234 error: Validation errors when converting ezxmltext for contentobject_attribute.id=1234 - context : Error in 2:0: Element section has extra content: informaltable ``` The first log message is a notice about the `ez-temporary=1` attribute which the conversion tool simply removes during conversion. The second log message is an error, but the cause described may be confusing. During the conversion, the `` element is converted to an `` tag, which is problematic. The exact problem in this case is the value of the second align attribute: `
    `. An align attribute may only have the following values: `left`, `right`, `center`, `justify`. To fix the problem, open the .xml file in a text editor and correct the errors: ```
    col1 col2 col3 col4
    ``` Now, you may test if the modified `ezxmltext` may be converted using the `--dry-run` and `--content-object` options: `php -d memory_limit=1536M bin/console ezxmltext:import-xml --dry-run --export-dir=ezxmltext-export --content-object=56554 -v` If the tool reports no errors, then the `ezxmltext` is fixed. You may rerun the command without the `--dry-run` option to actually update the database with the correct XmlText. Once you have fixed all the dump files in `ezxmltext-export/`, you may skip the `--content-object` option and the script imports all the dump files located in the `export-dir`: `php -d memory_limit=1536M bin/console ezxmltext:import-xml --export-dir=ezxmltext-export -v` Typical problems that needs manual fixing: **Duplicate XHTML IDs** XHTML IDs needs to be unique. The following `ezxmltext` results in a warning: ``` link with id inv5 another link with id inv5 ``` The conversion tool replaces the duplicate id (`inv5`) with a random value. If you need the ID value to match your CSS, you need to change it manually. The conversion tool also complains about IDs which contain invalid characters. **Links with non-existing `object_remote_id` or `node_remote_id`.** In `ezxmltext` you may have links which refer to other objects by their remote ID. This isn't supported in `richtext`, so the conversion tool must look up such remote IDs and replace them with the `object_id` or `node_id`. If the conversion tool cannot find the object by its remote id, it issues a warning about it. In older eZ Publish databases you may also have invalid links due to lack of reference to a target (for example, no `href` or `url_id`): ``` some text ``` When the conversion tool detects links with no reference it issues a warning and rewrite the URL to point to current page (`href="#"`). **``** The `` tag isn't yet supported in eZ Platform. When you're ready to migrate your eZ Publish XmlText content to the eZ Platform RichText format and start using pure eZ Platform setup, start the conversion script without the `--dry-run` option. Execute the following from : `php -d memory_limit=1536M bin/console ezxmltext:convert-to-richtext --export-dir=ezxmltext-export --export-dir-filter=notice,warning,error --concurrency 4 -v` **Custom tags and attributes** eZ Platform now supports custom tags, including inline custom tags, and limited use of custom tag attributes. After migrating to RichText, you need to adapt your custom tag config for eZ Platform and rewrite the custom tags in Twig. See [Custom tag documentation](https://doc.ibexa.co/en/2.5/guide/extending/extending_online_editor/#custom-tags) for more info. If you configured custom attributes in legacy in OE by using [ezoe_attributes.ini](https://github.com/ezsystems/ezpublish-legacy/blob/master/extension/ezoe/settings/ezoe_attributes.ini#L33-L48), not all types are supported. Below is a table of the tags that are currently supported, and their corresponding names in eZ Platform. | [XmlText](https://github.com/ezsystems/ezpublish-legacy/blob/2019.03/extension/ezoe/settings/ezoe_attributes.ini#L33-L48) | [RichText](https://github.com/ezsystems/ezplatform-richtext/blob/v1.1.5/src/bundle/DependencyInjection/Configuration.php#L17) | Note | | ------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | -------------------------- | | `link` | [`link`](https://doc.ibexa.co/en/2.5/guide/extending/extending_online_editor/#link-tag) | | | `number` | `number` | | | `int` | `number` | | | `checkbox` | `boolean` | | | `select` | `choice` | | | `text` | `string` | | | `textarea` | Not supported | Use `string` as workaround | | `email` | Not supported | Use `string` as workaround | | `hidden` | Not supported | Use `string` as workaround | | `color` | Not supported | Use `string` as workaround | | `htmlsize` | Not supported | Use `string` as workaround | | `csssize` | Not supported | Use `string` as workaround | | `csssize4` | Not supported | Use `string` as workaround | | `cssborder` | Not supported | Use `string` as workaround | #### 3.2.2 Migrate page field to page (eZ Enterprise only) **If** you use page field (ezflow) and an eZ Enterprise subscription, and are ready to migrate your eZ Publish Flow content to the eZ Enterprise page format, you can use a script to migrate your old page content to new page, to start using a pure eZ Enterprise setup. For more information, see [Migrating legacy Page field (ezflow) to new Page (Enterprise)](#migrating-legacy-page-field-ezflow-to-new-page-enterprise). #### 3.2.3 Add other eZ Enterprise schemas (eZ Enterprise only) For date-based publisher and Form Builder, there are additional tables, you can import them to your database using the following sql files: `/vendor/ezsystems/date-based-publisher/bundle/Resources/install/datebasedpublisher_scheduled_version.sql`, `/vendor/ezsystems/ezstudio-form-builder/bundle/Resources/install/form_builder.sql`, `/vendor/ezsystems/ezstudio-notifications/bundle/Resources/install/ezstudio-notifications.sql` ## Step 4: Re-configure web server and proxy ### Varnish *(optional)* If you use Varnish, the recommended Varnish (4 or higher) VCL configuration can be found in the [Using Varnish](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/http_cache/reverse_proxy/#configure-varnish-and-fastly) page. ### Web server configuration The officially recommended virtual configuration is now shipped in the `doc` folder, for both apache2 (`doc/apache2`) and nginx (`doc/nginx`). Both are built to be easy to understand and use, but aren't meant as drop-in replacements for your existing configuration. As was the case starting 5.4, one notable change is that `SetEnvIf` is now used to dynamically change rewrite rules depending on the Symfony environment. It's currently used for the assetic production rewrite rules. ## Step 5: Link assets Assets from the various bundles need to be made available for the webserver through the web/ document root. Execute the following commands from ``: ``` php bin/console assets:install --env=prod --symlink php bin/console assetic:dump --env=prod ``` ## Potential pitfalls ##### Unstyled login screen after upgrade It's possible that after the upgrade your admin screen is unstyled. This may happen because the new SiteAccess isn't available in the database. You can fix it by editing the permissions for the Anonymous user. Go to **Roles** in the **Admin** panel and edit the limitations of the Anonymous user's `user/login` policy. Add all SiteAccesses to the limitation, save, and clear the browser cache. The login screen should now show proper styling. ##### Translating URLs If your legacy site uses old-style URL aliases, to upgrade them successfully you need to apply a workaround to the slug converter. Where the slug converter service is defined, set second config parameter to use `urlalias_compat` by adding a new argument to the existing settings: ``` # in vendor/ezsystems/ezplatform-kernel/eZ/Publish/Core/settings/storage\_engines/common.yaml ezpublish.persistence.slug_converter: class: '%ezpublish.persistence.slug_converter.class%' arguments: - '@ezpublish.api.storage_engine.transformation_processor' - { transformation: urlalias_compat } ``` In case of URLs with extended UTF-encoded names, the workaround must make use of `urlalias_iri`: ``` ezpublish.persistence.slug_converter: class: '%ezpublish.persistence.slug_converter.class%' arguments: - '@ezpublish.api.storage_engine.transformation_processor' - { transformation: urlalias_iri } ``` ## Migrating legacy page field (ezflow) to new page (Enterprise) To move your legacy page field / eZ Flow configuration to eZ Platform Enterprise Edition you can use a script that aids in the migration process. The script automatically migrates only data – to move, for example, custom views, layouts, or blocks, you have to provide their business logic again. > **Caution: Caution** > > The migration script operates on your current database. > > Make sure to **back up your database** in case of an unexpected error. To use the script, do the following: > **Note: Note** > > Make a note of the paths to .ini files which define your legacy blocks. You need these paths later. **1.** Add `ezflow-migration-toolkit` to `composer.json` in your clean Platform installation. ``` "ezsystems/ezflow-migration-toolkit": "^1.0.0" ``` **2.** Add `ezflow-migration-toolkit` to `AppKernel.php`. ``` // AppKernel.php new EzSystems\EzFlowMigrationToolkitBundle\EzSystemsEzFlowMigrationToolkitBundle() ``` **3.** Clear cache. ``` bin/console cache:clear ``` **4.** Run the script with the following parameters: - absolute path of your legacy application - list of .ini files which define your legacy blocks **Script command** ``` bin/console ezflow:migrate —ini= [—ini= ...] ``` **Example of the migration script command** ``` bin/console ezflow:migrate /var/www/legacy.application.com/ —ini=extension/myapplication/settings/block.ini.append.php ``` **5.** You're warned about the need to create a [backup](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/backup/index.md) of your database. **Proceed only if you're sure you have done it.** A `MigrationBundle` is generated in the `src/` folder. You can see a report summarizing the results of the migration. **6.** Add `MigrationBundle` to `AppKernel.php`. ``` // AppKernel.php new MigrationBundle\MigrationBundle() ``` **7.** Clear cache again. At this point you can already view the initial effects of the migration, but they're still missing some of your custom content. The `MigrationBundle` generates placeholders for layouts in the form of frames with a data dump. For blocks that could not be mapped to existing Page blocks, it also generates PHP file templates that you need to fill with your own business logic. # Migrating from eZ Publish eZ Publish was eZ Platform's predecessor, a CMS in development for five major versions and several years. Users of eZ Publish can find eZ Platform largely similar to what they know. The improvements and enhancements did not turn the fundamental concepts underlying the system, such as the content model, upside down. However, specific features, solutions, and recipes may work differently between the two versions. The release of eZ Platform brought about an inevitable disruption in backwards compatibility with eZ Publish. This means that the process of migrating existing installations requires more effort than simply upgrading from one version to another. Here you can find details on moving existing Publish-powered websites to eZ Platform. ## Changes overview ### Incompatibilities with legacy eZ Platform represents the 6th generation of eZ Publish, and while the 5th generation had\* *a major focus on backwards code compatibility with the 3rd and 4th generations \*(legacy)*, the 6th generation doesn't. The 6th generation is aimed at being fully backwards compatible on the following: - **Content** from 4th and later 5th generation installation - **Code** from 5th generation system when written for *Platform (Symfony) stack* The specific incompatibilities The specific changes that are migrated and are incompatible with legacy are: - XmlText fields have been replaced with a new [RichText](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/richtextfield/index.md) field - Page field (ezflow) has been replaced by the [LandingPage](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/pagefield/index.md) field, and is now provided by our commercial product [eZ Platform Enterprise Edition](http://ezstudio.com/) - Incremental future improvements to the database schema to improve features and scalability of the content repository Together these major improvements make it practically impossible to run eZ Platform side by side with eZ Publish legacy, like it was possible in 5.x series. *For these reasons we recommend that you use eZ Publish Enterprise 5.4  ([which is supported until end of 2021](https://support.ez.no/Public/Service-Life)) if you don't have the option to remake your web application yet, or want to do it gradually.* ## Migration Path ### From legacy (4.x or 5.x) to Platform stack (5.4/2014.11) If you're coming directly from legacy (4.x), you need to follow the best practice 5.x Platform migration path and do the following: - Rewrite custom field types for the new Platform stack. Alternatively you can use Null field type as a dummy implementation for the custom field types that you don't want to migrate. Using Null field type prevents errors from the Platform Stack, see [Null field type Reference](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/nullfield/index.md) - Rewrite custom web front end to use the new Platform/Symfony stack, see [Beginner Tutorial](https://doc.ibexa.co/en/latest/tutorials/beginner_tutorial/beginner_tutorial/index.md) - Rewrite custom admin modules to use the new Platform/Symfony stack - And if you do this while on 5.x, you can use several of the [available legacy migration features](https://doc.ez.no/display/EZP/Legacy+code+and+features) to make the new code appear in legacy admin See Upgrade documentation on how to perform the actual upgrade: [Upgrade (eZ Publish Platform page)](https://doc.ez.no/display/EZP/Upgrade). > **Caution: Avoid exception when migrating the database** > > If you plan to migrate from from eZ Publish through eZ Publish Platform 5.4 to eZ Platform and further, an exception may occur when you try to migrate the database while it contains internal drafts of landing pages. This can happen because such drafts don't have an expected row in the `ezcontentobject_name` table. > > To avoid this exception, you must remove all internal drafts before you migrate. First, in `content.ini`, set the `InternalDraftsCleanUpLimit` and `InternalDraftsDuration` values to 0. Then run the [internal drafts cleanup](https://github.com/ezsystems/ezpublish-legacy/blob/2019.03/cronjobs/internal_drafts_cleanup.php) cron job. ### From Platform stack (5.4/2014.11) to eZ Platform As eZ Platform introduced completely new user interfaces with greatly improved user experience, the following custom developments needs to be made if you have customization needs: - Write UI code for custom field types for the new JavaScript-based editorial interface (see [Page blocks](https://doc.ibexa.co/en/latest/templating/render_content/render_page/index.md)) - Adjust custom admin modules for the new Symfony-based admin interface For a detailed guide through these developments see [Upgrading from 5.4.x and 2014.11 to eZ Platform](https://doc.ibexa.co/en/latest/update_and_migration/migrate_to_ibexa_dxp/migrating_from_ez_publish_platform/index.md). # Common migration issues Below you can find cleanup commands from the EzPublishMigrationBundle for the most common issues that can occur after migration to Ibexa DXP. > **Note: Enabling EzPublishMigrationBundle bundle** > > To enable EzPublishMigrationBundle add the following to your `dev` environment bundles in `app/AppKernel.php`: > > ``` > $bundles[] = new \eZ\Bundle\EzPublishMigrationBundle\EzPublishMigrationBundle(); > ``` > **Caution: Caution** > > Remember about [**proper backup**](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/backup/index.md) before running any of the commands below. ## Regenerating URL aliases To regenerate URL aliases, use the `ibexa:urls:regenerate-aliases` command. For more information, see [Regenerating URL aliases](https://doc.ibexa.co/en/latest/content_management/url_management/url_management/#regenerating-url-aliases). > **Note: Note** > > This command keeps history and replaces the old `ibexa:regenerate:legacy_storage_url_aliases` command. `legacy_storage_url_aliases` is now deprecated. ## Normalizing images If you use image files with unprintable UTF-8 characters, you may come across a problem with images not displaying. In that case, run the `ezplatform:images:normalize-path` command to normalize them: ``` php bin/console ezplatform:images:normalize-path ``` > **Note: Special characters** > > If a corrupted path is detected, check the `var_dir` configuration for any special or Unicode characters. ## Unknown relation type 0 "Unknown relation type 0." error occurs only when using REST API. The issue doesn't occur the first time article is published (upon creation). It only happens after the article is edited and published. If this error occurs use the console command below. It cleans up redundant Relations rows: ``` php bin/console ezpublish:update:legacy_storage_clean_up_relation_type_eq_zero ``` The command can be executed in two modes: - list / dry-run - prints table with all corrupted Relations that are deleted (to be executed first) - fix - executes clean up ## Always available flag set on all fields Always available flag is set on all fields, instead of only on fields in the main language. This problem occurs when Ibexa DXP is used to create content that is both always available and has multiple translations. The cleanup script correctly sets always available flag for prioritized language filtering in Legacy search engine. ``` php bin/console ezpublish:update:legacy_storage_fix_fields_always_available_flag ``` Only affected fields are processed by the cleanup command. ## Listing sub-content It's possible that after upgrade `sort_key_string` is left empty. This may cause problems in searches throughout the API. The cleanup script checks if fields of given field type have correct sort key, and update it if needed. Execute the following command from the installation root directory: ``` php bin/console ezpublish:update:legacy_storage_update_sort_keys ``` # Resources # Resources See additional information about Ibexa DXP development process, helpful tools, and learn how you can contribute to the creation of the platform. - [Ibexa DXP release process and roadmap](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/resources/release_process_and_roadmap/): Ibexa DXP releases new versions periodically in different flavors: Ibexa Headless, Ibexa Experience and Ibexa Commerce, plus open-source Ibexa OSS. - [Ibexa DXP plugin for PhpStorm](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/resources/phpstorm_plugin/): The Ibexa DXP PhpStorm plugin helps you speed up your development by providing file templates, autocompletion, a quick installation wizard, and more. - [Report and follow issues](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/resources/contributing/report_and_follow_issues/): You can report encountered issues to Service Portal # Ibexa DXP release process and roadmap ## Release process ### Distributions Ibexa DXP has three distributions: - Ibexa Headless is a multichannel and headless content management system. - Ibexa Experience is a modern modular Digital Experience Platform to build outstanding customer experiences - Ibexa Commerce is a commerce-ready B2B DXP designed to digitalize your business from customer awareness to purchase and beyond. Additionally, Ibexa DXP also has an open-source version called Ibexa OSS. Ibexa OSS is developed by Ibexa together with the open source community. The Ibexa OSS code is available on GitHub under the GPLv2 license. It comes with no commercial support and maintenance services. ### Long Term Support releases Ibexa manages the release of Ibexa DXP by using an agile iterative process and a continuous software development model, which is why we provide Long Term Support releases (LTS) of Ibexa DXP releases. Long Term Support releases (LTS) are supported by Ibexa for a long period of time. They're suitable for highly stable enterprise rollouts. LTS releases provide you with: - **Reliability and stability**, as they go through extensive testing to ensure they are free from major bugs and issues. - **Long-term security**, as a result of updates and security patches. - **Predictability** that comes from following an established release plan. - **Reduced maintenance**, because you avoid the frequent upgrade cycles. ### LTS Updates With LTS Updates customers can maintain their competitiveness by incorporating cutting-edge technologies into their LTS releases without losing stability. LTS Updates are intended to improve the current platform by providing new features. What's important, you are not required to switch to a newer version of Ibexa DXP to use LTS Updates. You can install them whenever you choose, and you can be sure that the next LTS release will include them by default. You won't have to manually install or configure them after upgrading, so you can make the switch smoothly when the time comes. ## Versioning conventions All Ibexa DXP editions use [semantic versioning](https://semver.org/). The version number of Ibexa DXP and all its internal components follows the semantic versioning conventions: vX.Y.Z. - Changes to X indicate breaking changes. They usually concern mostly internal things, but developers should check in our change logs if they need to adjust their code to continue using the API or features. If there are larger breaks, this is announced well in advance of the upcoming release. - Y represents new features and functionalities. - Z represents patches, bug fixes, or smaller improvements. Distribution files of Ibexa three editions are as follows: - for Ibexa Headless: ibexa-headless-vX.Y.Z.tgz - for Ibexa Experience: ibexa-experience-vX.Y.Z.tgz - for Ibexa Commerce: ibexa-commerce-vX.Y.Z.tgz Ibexa's support and maintenance services specific to each release are only available from a given start date until an end date. The time in between the start and end dates is what Ibexa calls the product's **Service Life**. You can find the specific dates of service life for each release on Ibexa [service life page](https://support.ibexa.co/Public/Service-Life). # Ibexa DXP plugin for PhpStorm Ibexa DXP plugin for PhpStorm helps you to work with Ibexa DXP by speeding up installation and providing file templates, intentions, autocompletion, and other features. ## Requirements - PhpStorm 2021.2 or newer - Enabled Symfony support plugin ## Install PhpStorm plugin You can install the Ibexa DXP plugin for PhpStorm from the JetBrains Marketplace, or manually, from a downloaded .jar file. ### Install from JetBrains Marketplace To install plugin from JetBrains marketplace: Look for "Ibexa DXP" in the plugin browser and click **Install**. ### Install from file You can also install the plugin manually from a `.jar` file: 1. Download the latest version of the plugin from [JetBrains Marketplace](https://plugins.jetbrains.com/plugin/17239-ibexa-dxp/versions). 1. In PhpStorm settings/preferences (depending on your system), select **Plugins** > (gear icon) > **Install plugin from Disk...** and select the downloaded file. ## Configuration Plugin configuration is available in PhpStorm settings/preferences (depending on your system), under **PHP** > **Frameworks** > **Ibexa DXP**. You can use it to: - Enable and disable plugin features for the current project - Change product edition and version by the current project *[Image: Intention]* > **Note: Note** > > Some plugin features depends on the selected product edition and version. For example, "deprecated namespaces usage" inspection is enabled only if the project uses v4.x. Plugin configuration is automatically resolved when opening Ibexa DXP project for the first time. If detection is successful, a notification appears with an "Enable Ibexa DXP support for this project" link. If you created your project by using Ibexa DXP project wizard, the plugin is automatically enabled and configured based on wizard data. ## Features ### Project wizard The plugin enables creating a new Ibexa DXP project directly from PhpStorm. To do it, select **File** > **New Project...** > **Ibexa DXP**. In project settings form you can choose: - Location of the project - Product edition: Ibexa OSS, Ibexa Headless, Ibexa Experience, Ibexa Commerce - Authentication token (for Content, Experience and Commerce editions) - Product version: Default (latest LTS version), Latest (fast track or LTS), Latest LTS and "Next 3.x" (unstable, based on the 3.x branch) and "Next 4.x" (unstable, based on the 4.x branch) - Generate [Ibexa Cloud configuration](https://doc.ibexa.co/en/latest/ibexa_cloud/install_on_ibexa_cloud/index.md) - Composer settings *[Image: Create a project]* If you don't provide credentials for , the plugin uses the installation key and token password stored in global Composer configuration. Otherwise, it creates an `auth.json` file. You can find details of the installation procedure in Composer log window. ### File templates The plugin provides the following built-in file templates: | Name | Comment | | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | | Back office tab | Class implementing `Ibexa\Contracts\AdminUi\Tab` | | Block event subscriber | Event subscriber for `BlockRenderEvents::getBlockPreRenderEventName(...)` event | | Command | Symfony command that uses content repository | | Composite Criterion | Criterion class based on `\Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion\CompositeCriterion` | | Field definition form mapper | Class implementing `Ibexa\AdminUi\FieldType\FieldDefinitionFormMapperInterface` | | Field type | Field type class based on `Ibexa\Contracts\Core\FieldType\Generic\Type` | | Field type Comparable | Class implementing `Ibexa\Contracts\VersionComparison\FieldType\Comparable` | | Field type Indexable | Class implementing `Ibexa\Contracts\Core\FieldType\Indexable` | | Field value form mapper | Class implementing `Ibexa\Contracts\ContentForms\FieldType\FieldValueFormMapperInterface` | | Field value object | Field type value class | | Installer Provisioner | Class implementing `\Ibexa\Installer\Provisioner\ProvisionerInterface` | | Menu configuration event subscriber | Event subscriber for `Ibexa\AdminUi\Menu\Event\ConfigureMenuEvent::MAIN_MENU` | | Policy provider | Class implementing `Ibexa\Bundle\Core\DependencyInjection\Security\PolicyProvider\PolicyProviderInterface` | | Policy provider (YAML) | Policy provider class based on `Ibexa\Bundle\Core\DependencyInjection\Security\PolicyProvider\YamlPolicyProvider` | | Query Type | Query Type class based on `Ibexa\Core\QueryType\OptionsResolverBasedQueryType` | | Schema builder subscriber | Event subscriber for `Ibexa\Contracts\DoctrineSchema\Event\SchemaBuilderEvent::BUILD_SCHEMA` event | | SiteAccess-aware configuration | SiteAccess-aware configuration definition class based on `Ibexa\Bundle\Core\DependencyInjection\Configuration\AbstractParser` | | Value object input parser | REST input parser class based on `Ibexa\Rest\Input\BaseParser` | | Value object visitor | REST value visitor class based on `Ibexa\Contracts\Rest\Output\ValueObjectVisitor` | | Workflow action listener | Workflow action listener class based on `Ibexa\Contracts\Workflow\Event\Action\AbstractTransitionWorkflowActionListener` | The templates are available in, for example, the context menu in **Project window** > **New** > **Ibexa DXP**. The list of available file templates depends on the Ibexa DXP edition used by the project. For all file templates you can customize: - class name - class namespace - file name - directory *[Image: File template]* To customize file templates, go to **File** > **Settings**/**Preferences** > **Editor** > **File and Code templates**. > **Tip: Tip** > > For more information about file templates, see [JetBrains documentation](https://www.jetbrains.com/help/phpstorm/settings-file-and-code-templates.html). ### Live templates The plugin provides the following built-in live templates in Twig files: | Abbreviation | Comment | | ------------ | -------------------------------------------------------------------- | | `ezcn` | `ibexa_content_name` | | `ezfd` | `ibexa_field_description` | | `ezfd?` | `ibexa_field_description` wrapped in an `ibexa_field_is_empty` check | | `ezfn` | `ibexa_field_name` | | `ezfn?` | `ibexa_field_name` wrapped in an `ibexa_field_is_empty` check | | `ezrc` | `ibexa_render_content` | | `ezrcq` | `ibexa_render_content_query` | | `ezrf` | `ibexa_render_field` | | `ezrf?` | `ibexa_render_field` wrapped in an `ibexa_field_is_empty` check | | `ezrl` | `ibexa_render_location` | | `ezrlq` | `ibexa_render_location_query` | and in PHP files: | Abbreviation | Comment | | ----------------- | ------------------------------------- | | `ibx_create_c` | Create content | | `ibx_create_cd` | Create content draft | | `ibx_create_ct` | Create content type | | `ibx_find_c` | Create and execute content query | | `ibx_find_ci` | Create and execute content info query | | `ibx_find_l` | Create and execute location query | | `ibx_load_c` | Load content by ID | | `ibx_load_ci` | Load content info by ID | | `ibx_load_ct` | Load content type by identifier | | `ibx_load_l` | Load location by ID | | `ibx_param` | Get SiteAccess parameter value | | `ibx_pub` | Publish content draft | | `ibx_switch_user` | Switch user context | | `ibx_trans` | Repository transaction | | `ibx_update_c` | Update content | | `ibx_update_ct` | Update content type | To customize live templates, go to **File** > **Settings**/**Preferences** > **Editor** > **Live Templates**. > **Tip: Tip** > > For more information about live templates, see [JetBrains documentation](https://www.jetbrains.com/help/idea/using-live-templates.html). ### Autocompletion in configuration files Plugin provides autocompletion for Ibexa DXP configuration structure in YAML files placed in `config/packages/`. Besides configuration structure, for the following YAML keys addition suggestions are available: - List of available view matchers, for: - `ibexa..content_view...match` - `ibexa..content_create_view...match` - `ibexa..content_edit_view...match` - `ibexa..content_translate_view...match` - List of available SiteAccess matchers, for: - `ibexa.siteaccess.match` - List of available block attribute types, for: - `ibexa_fieldtype_page.blocks..attributes..type` - List of available configuration scopes, for: - `ibexa` - List of available siteaccess names, for: - `ibexa.siteaccess.default_siteaccess` - `ibexa.siteaccess.groups` - `ibexa.system..translation_siteaccesses$` - List of available design names, for: - `ibexadesign.design_list` - `ibexa.system..design` - List of available repositories, for: - `ibexa.system..repository` - List of available search engines, for: - `ibexa.repositories..search.engine` - List of available custom tags, for: - `ibexa.system..fieldtypes.ibexa_richtext.custom_tags` - List of available view types, for: - `ibexa..content_view` - `ibexa..content_create_view` - `ibexa..content_edit_view` - `ibexa..content_translate_view` ### Structure autocompletion in DBAL schema file Autocompletion is also available for DBAL schema file structure. To enable autocompletion, you must place the file in the `config` directory and name it `schema.yaml`. ### Dynamic settings autocompletion Parameter names suggestions are available in `Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface::{hasParameter,getParameter}` method calls. Suggested results take into account namespace argument, if its value can be resolved without running interpreter (for example, string literal or const reference). ### Query type name autocompletion Query type name suggestions are available in `Ibexa\Core\QueryType\QueryTypeRegistry::getQueryType` method calls. Suggestions are based on service definitions tagged as `ibexa.query_type`. ### Query type parameter autocompletion Parameter name suggestions are available for Query types which implement the `Ibexa\Core\QueryType\QueryType` interface or extend the `Ibexa\Core\QueryType\OptionsResolverBasedQueryType` class in the following places: - `Ibexa\Core\QueryType\QueryType::getQuery` method calls - `Ibexa\Core\QueryType\QueryType::getQuery` method definition - `Ibexa\Core\QueryType\OptionsResolverBasedQueryType::doGetQuery` method definition *[Image: Query Type parameter autocompletion]* ### Intentions and inspections The plugin also brings several new intentions and inspections (with related quick fixes where possible). For example, when plugin detects deprecated configuration key usage, it marks the key as deprecated and suggests a replacement: *[Image: Intention]* ## Known issues It's not possible to create new project with Docker as PHP remote interpreter. See [related JetBrains issue](https://youtrack.jetbrains.com/issue/WI-61330) for more details. # New in documentation This page contains recent highlights and notable changes in Ibexa DXP documentation. ## March 2026 ### Products - Distinguish [physical and virtual products in product catalog guide](https://doc.ibexa.co/en/5.0/pim/pim_guide/#virtual-and-physical-products) - Added [creating product type example in product API](https://doc.ibexa.co/en/5.0/pim/product_api/#creating-product-types) - Detailed [how to add a custom attribute type to an existing storage definition](https://doc.ibexa.co/en/5.0/pim/create_custom_attribute_type/#storage-definition) ### Collaborative editing - Documented [how to extend collaboration](https://doc.ibexa.co/en/5.0/content_management/collaborative_editing/extend_collaborative_editing/) with a shared cart example ### Search - Introduced [embeddings search API for taxonomy](https://doc.ibexa.co/en/5.0/search/embeddings_reference/embeddings_reference/) ### Images - Illustrated [image optimizer customization](https://doc.ibexa.co/en/5.0/content_management/images/images/#customizing-image-optimizers) ## February 2026 ### Releases - [v5.0.6 release notes](https://doc.ibexa.co/en/5.0/release_notes/ibexa_dxp_v5.0/#ibexa-dxp-v506) - [v4.6.28 release notes](https://doc.ibexa.co/en/4.6/release_notes/ibexa_dxp_v4.6/#ibexa-dxp-v4628) ### CDP - Illustrated how to [export additional user data](https://doc.ibexa.co/en/latest/cdp/cdp_data_customization/#export-additional-user-data) - Detailed [Ibexa Messenger support for large batches of data](https://doc.ibexa.co/en/latest/cdp/cdp_activation/cdp_data_export/#ibexa-messenger-support-for-large-batches-of-data) - Introduced the [CDP Monolog channel](https://doc.ibexa.co/en/latest/cdp/cdp_activation/cdp_data_export/#cdp-monolog-channel) - Added [`Ibexa\Contracts\Cdp` PHP API reference](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/namespaces/ibexa-contracts-cdp.html) ### Infrastructure #### Modified 5.0 update instructions To [update from v5.0.x to v5.0.latest](https://doc.ibexa.co/en/latest/update_and_migration/from_5.0/update_from_5.0/index.md), you have to ensure that Yarn dependencies are up-to-date before running Composer. #### Ibexa Cloud - Added documentation describing [how to use the new `ibexa/cloud` package](https://doc.ibexa.co/en/latest/ibexa_cloud/install_on_ibexa_cloud/index.md) and the [environment variables it provides](https://doc.ibexa.co/en/latest/ibexa_cloud/environment_variables/index.md) #### DFS configuration - Updated [DFS](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/clustering/clustering/#configuring-the-dfs-io-handler) and [Solr](https://doc.ibexa.co/en/latest/search/search_engines/solr_search_engine/install_solr/#configure-the-bundle) configuration examples to use environment variables directly with [Environment Variable Processors](https://symfony.com/doc/7.4/configuration/env_var_processors.html) syntax instead of intermediate parameters. This promotes skipping the rebuild of the Symfony container when environment variable values change. ## January 2026 ### Releases - [v5.0.5 release notes](https://doc.ibexa.co/en/5.0/release_notes/ibexa_dxp_v5.0/#ibexa-dxp-v505) - [v4.6.27 release notes](https://doc.ibexa.co/en/4.6/release_notes/ibexa_dxp_v4.6/#ibexa-dxp-v4627) ### Getting started - Added a description of password constraints in [Install Ibexa DXP](https://doc.ibexa.co/en/latest/getting_started/install_ibexa_dxp/#create-a-database) and [Install with DDEV](https://doc.ibexa.co/en/latest/getting_started/install_with_ddev/#6-install-the-platform-and-its-database) ### Administration - Updated icon function and icon set usage instructions in [Custom icons](https://doc.ibexa.co/en/latest/administration/back_office/back_office_elements/custom_icons/index.md) ### Discounts - Added Ibexa Messenger instructions for Ibexa DXP 4.6 - Modified [discounts configuration](https://doc.ibexa.co/en/4.6/discounts/configure_discounts/#discount-re-indexing) - Added [installation instructions](https://doc.ibexa.co/en/4.6/infrastructure_and_maintenance/background_tasks/#installation) ### Multisite - Site Factory - Fixed an issue with [design configuration](https://doc.ibexa.co/en/latest/multisite/site_factory/site_factory/#configure-designs) ### Search - Added support for Elasticsearch 8.19+: - Updated the [requirements](https://doc.ibexa.co/en/latest/getting_started/requirements/index.md) - Updated the [Elasticsearch overview](https://doc.ibexa.co/en/latest/search/search_engines/elasticsearch/elasticsearch_overview/index.md) - Modified the [configuration instructions](https://doc.ibexa.co/en/latest/search/search_engines/elasticsearch/configure_elasticsearch/index.md) - Modified the [installation instructions](https://doc.ibexa.co/en/latest/search/search_engines/elasticsearch/install_elasticsearch/index.md) - Modified the [system update instructions](https://doc.ibexa.co/en/latest/update_and_migration/from_5.0/update_from_5.0/index.md) ### Infrastructure and maintenance - Added [reverse proxy installation instructions](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/clustering/clustering_with_ddev/#install-reverse-proxy) to DDEV cluster description - Modified the [system update instructions](https://doc.ibexa.co/en/latest/update_and_migration/from_5.0/update_from_5.0/index.md) to account for numerous changes in the product - Detailed the Varnish [reverse proxy configuration instructions](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/http_cache/reverse_proxy/#vcl-base-files) by mentioning specific VCL files that must be used ## December 2025 ### Discounts - Extending Discounts - Added documentation about how to [create custom conditions and rules](https://doc.ibexa.co/en/latest/discounts/extend_discounts/#create-custom-conditions-and-rules) and [change discount priority](https://doc.ibexa.co/en/latest/discounts/extend_discounts/#change-discount-priority) - Added documentation about how to [extend Discounts wizard](https://doc.ibexa.co/en/latest/discounts/extend_discounts_wizard/) - Example [how to deal with discounts products using the API](https://doc.ibexa.co/en/latest/discounts/discounts_api/#example-api-usage) added in the Discounts API page ### Infrastructure - Updated [requirements](https://doc.ibexa.co/en/4.6/getting_started/requirements/) for Ibexa DXP 4.6 and 5.0: PostgreSQL 18 is now officially supported ### Ibexa Cloud - Naming update in the documentation: [renamed Platform.sh to Upsun](https://upsun.com/platform-sh-is-now-upsun/) ## November 2025 ### Releases - [v5.0.4 release notes](https://doc.ibexa.co/en/5.0/release_notes/ibexa_dxp_v5.0/#ibexa-dxp-v504) - [v4.6.26 release notes](https://doc.ibexa.co/en/4.6/release_notes/ibexa_dxp_v4.6/#ibexa-dxp-v4626) ### Infrastructure - [MariaDB 11.4 is officially supported on v5 and v4.6](https://doc.ibexa.co/en/latest/getting_started/requirements/#dbms) ### Taxonomy - Described [Taxonomy suggestions](https://doc.ibexa.co/en/latest/content_management/taxonomy/taxonomy/#taxonomy-suggestions) ### Collaborative editing - Improved Collaborative editing: - Added [real-time editing configuration](https://doc.ibexa.co/en/latest/content_management/collaborative_editing/configure_collaborative_editing/#configure-real-time-editing) - Added Collaborative editing policies for [content items](https://doc.ibexa.co/en/latest/permissions/policies/#content-collaborative-editing) and [products](https://doc.ibexa.co/en/latest/permissions/policies/#product-collaborative-editing) - Added [Collaborative editing limitations](https://doc.ibexa.co/en/latest/permissions/limitation_reference/#collaborative-editing-limitations) ### Search - Expended search API to content types: - [Finding and filtering content types](https://doc.ibexa.co/en/latest/content_management/content_api/managing_content/#finding-and-filtering-content-types) - [Content Type Search Criteria reference](https://doc.ibexa.co/en/latest/search/content_type_search_reference/content_type_criteria/index.md) - [Content Type Search Sort Clauses](https://doc.ibexa.co/en/latest/search/content_type_search_reference/content_type_sort_clauses/index.md) - [Filter content types REST resource](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Type/operation/api_contenttypesview_post) ### LTS Updates - Added documentation for Integrated help (v5 and v4.6): - [how to install](https://doc.ibexa.co/en/latest/administration/back_office/integrated_help/index.md) - [how to customize](https://doc.ibexa.co/en/latest/administration/back_office/customize_integrated_help/index.md) - Added [Anthropic connector installation](https://doc.ibexa.co/en/latest/ai_actions/configure_ai_actions/#install-anthropic-connector) (v5 only) ### DDEV - Updated [Solr installation in DDEV cluster](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/clustering/clustering_with_ddev/#solr) ### PHP API - Enhanced the PHP API reference with the following new classes for Ibexa DXP 4.6 and 5.0: - [`Ibexa\Contracts\Elasticsearch\Query\EmbeddingVisitor`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Elasticsearch-Query-EmbeddingVisitor.html) - [`Ibexa\Contracts\AdminUi\ContentType\ContentTypeFieldsByExpressionServiceInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-AdminUi-ContentType-ContentTypeFieldsByExpressionServiceInterface.html) - [`Ibexa\Contracts\Collaboration\Invitation\Query\Criterion\ParticipantScope`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Invitation-Query-Criterion-ParticipantScope.html) - [`Ibexa\Contracts\Collaboration\Invitation\Query\Criterion\ParticipantType`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Invitation-Query-Criterion-ParticipantType.html) - [`Ibexa\Contracts\Collaboration\Participant\ParticipantDiscriminator`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Participant-ParticipantDiscriminator.html) - [`Ibexa\Contracts\ConnectorAi\Action\DataType\Taxonomy`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-DataType-Taxonomy.html) - [`Ibexa\Contracts\ConnectorAi\Action\DataType\TaxonomyEntry`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-DataType-TaxonomyEntry.html) - [`Ibexa\Contracts\ConnectorAi\Action\DataType\TaxonomySuggestion`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-DataType-TaxonomySuggestion.html) - [`Ibexa\Contracts\ConnectorAi\Action\DataType\TaxonomySuggestionInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-DataType-TaxonomySuggestionInterface.html) - [`Ibexa\Contracts\ConnectorAi\Action\DataType\TextToTaxonomyInput`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-DataType-TextToTaxonomyInput.html) - [`Ibexa\Contracts\ConnectorAi\Action\Response\TaxonomyResponse`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-Response-TaxonomyResponse.html) - [`Ibexa\Contracts\ConnectorAi\Action\SuggestTaxonomyAction`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-SuggestTaxonomyAction.html) - [`Ibexa\Contracts\ConnectorAi\Action\TextToTaxonomy`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/namespaces/ibexa-contracts-connectorai-action-texttotaxonomy.html) - [`Ibexa\Contracts\Core\Repository\Values\Content\EmbeddingQuery`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-EmbeddingQuery.html) - [`Ibexa\Contracts\Core\Repository\Values\Content\EmbeddingQueryBuilder`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-EmbeddingQueryBuilder.html) - [`Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContentTypeGroupName`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-ContentType-Query-Criterion-ContentTypeGroupName.html) - [`Ibexa\Contracts\Core\Repository\Values\Content\Query\Embedding`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Embedding.html) - [`Ibexa\Contracts\Core\Repository\Values\Content\QueryValidatorInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-QueryValidatorInterface.html) - [`Ibexa\Contracts\Core\Search\Embedding`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/namespaces/ibexa-contracts-core-search-embedding.html) - [`Ibexa\Contracts\Core\Search\FieldType\EmbeddingField`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Search-FieldType-EmbeddingField.html) - [`Ibexa\Contracts\Core\Search\FieldType\EmbeddingFieldFactory`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Search-FieldType-EmbeddingFieldFactory.html) - [`Ibexa\Contracts\FieldTypeRichTextRTE`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/namespaces/ibexa-contracts-fieldtyperichtextrte.html) - [`Ibexa\Contracts\Share\Mapper`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/namespaces/ibexa-contracts-share-mapper.html) - [`Ibexa\Contracts\Solr\Query\EmbeddingVisitor`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Solr-Query-EmbeddingVisitor.html) - [`Ibexa\Contracts\Taxonomy\Embedding`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/namespaces/ibexa-contracts-taxonomy-embedding.html) - [`Ibexa\Contracts\Taxonomy\Search\Query\Value\TaxonomyEmbedding`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Taxonomy-Search-Query-Value-TaxonomyEmbedding.html) - [`Ibexa\Contracts\User\PasswordReset\NotifierInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-User-PasswordReset-NotifierInterface.html) In addition, few only for 5.0 version: - [`Ibexa\Contracts\CoreSearch\Values\Query\PaginationAwareInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-CoreSearch-Values-Query-PaginationAwareInterface.html) - [`Ibexa\Contracts\ProductCatalog\CapabilitiesEnum`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-CapabilitiesEnum.html) - [`Ibexa\Contracts\ProductCatalog\CapabilitiesServiceInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-CapabilitiesServiceInterface.html) - [`Ibexa\Contracts\SiteFactory\Values\Query\Criterion\MatchTreeRootLocationIds`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-SiteFactory-Values-Query-Criterion-MatchTreeRootLocationIds.html) ### Contributions We want to thank - [romank](https://github.com/romank) for fixing a parse error in [Customize registration forms](https://doc.ibexa.co/en/latest/tutorials/beginner_tutorial/8_enable_account_registration/#customize-registration-forms) example - [todomagichere](https://github.com/todomagichere) for correcting `ibexa_path()` description in [URL Twig functions](https://doc.ibexa.co/en/latest/templating/twig_function_reference/url_twig_functions/index.md) ## October 2025 ### Releases - [v5.0.3 release notes](https://doc.ibexa.co/en/5.0/release_notes/ibexa_dxp_v5.0/#ibexa-dxp-v503) - [v4.6.25 release notes](https://doc.ibexa.co/en/4.6/release_notes/ibexa_dxp_v4.6/#ibexa-dxp-v4625) ### Collaborative editing - Described: - [installation and configuration](https://doc.ibexa.co/en/latest/content_management/collaborative_editing/configure_collaborative_editing/index.md) in more detail - [search criteria](https://doc.ibexa.co/en/latest/search/collaboration_search_reference/collaboration_criteria/index.md) and [sort clauses](https://doc.ibexa.co/en/latest/search/collaboration_search_reference/collaboration_sort_clauses/index.md) for [Collaborative editing](https://doc.ibexa.co/en/latest/content_management/collaborative_editing/collaborative_editing/index.md) - [events](https://doc.ibexa.co/en/latest/api/event_reference/collaboration_events/index.md) ### Extending Page Builder and Form builder - Expanded the [custom block for Page Builder](https://doc.ibexa.co/en/latest/content_management/pages/create_custom_page_block/index.md) and [custom field for Form Builder](https://doc.ibexa.co/en/latest/content_management/forms/create_custom_form_field/index.md) examples to showcase providing translations ### Search - Added documentation for [CustomField](https://doc.ibexa.co/en/latest/search/criteria_reference/customfield_criterion/index.md) criterion ### Localization - Added a note about disabling [community contributions](https://doc.ibexa.co/en/latest/resources/contributing/contribute_translations/index.md) for French, German and Spanish translations, in preparation for the upcoming release ### Trainings - The [Advanced Developer training](https://learn.ibexa.co) for Ibexa DXP v5 is now available ### Documentation improvements - Improved the landing page for [AI Actions](https://doc.ibexa.co/en/latest/ai_actions/ai_actions/index.md) for easier navigation - Improved the experience when switching between different versions of the documentation site ## September 2025 ### Releases - [v5.0.2 release notes](https://doc.ibexa.co/en/5.0/release_notes/ibexa_dxp_v5.0/#ibexa-dxp-v502) - [v4.6.24 release notes](https://doc.ibexa.co/en/4.6/release_notes/ibexa_dxp_v4.6/#ibexa-dxp-v4624) ### Background operations - Added documentation for handling [background tasks](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/background_tasks/index.md) using the new integration with Symfony Messenger - Described the configuration required to [asynchronously reindex discounted product prices](https://doc.ibexa.co/en/latest/discounts/configure_discounts/#discount-re-indexing) and the new discount [events](https://doc.ibexa.co/en/latest/api/event_reference/discounts_events/index.md) and [search criteria](https://doc.ibexa.co/en/latest/search/discounts_search_reference/discounts_criteria/index.md) ### Revamped notifications - Updated the [notifications](https://doc.ibexa.co/en/latest/administration/back_office/notifications/index.md) page after recent [improvements to notifications](https://doc.ibexa.co/en/latest/release_notes/ibexa_dxp_v4.6/#improvements-to-notifications), including the [new notification criteria](https://doc.ibexa.co/en/latest/search/criteria_reference/notification_search_criteria/index.md) ### Custom Page Builder blocks - Updated the [custom block example](https://doc.ibexa.co/en/latest/content_management/pages/create_custom_page_block/index.md), highlighting usage of [`udw_config_name` option](https://doc.ibexa.co/en/latest/content_management/pages/page_block_attributes/#block-attribute-types) to customize the [Universal Discovery Module](https://doc.ibexa.co/en/latest/administration/back_office/browser/browser/index.md) ### Migrations - Illustrated how to [send a location to trash](https://doc.ibexa.co/en/latest/content_management/data_migration/importing_data/#locations) using data migrations ### Search - Added documentation for the [`IsContainer`](https://doc.ibexa.co/en/latest/search/criteria_reference/iscontainer_criterion/index.md) criterion ### Collaborative editing - Added the [Collaborative editing product guide](https://doc.ibexa.co/en/latest/content_management/collaborative_editing/collaborative_editing_guide/index.md), describing the high-level overview of this feature - Described collaborative editing endpoints to the [REST API reference](https://doc.ibexa.co/en/4.6/api/rest_api/rest_api_reference/rest_api_reference.html#collaborative-editing) for Ibexa DXP v4.6 ### Infrastructure - Marked Redis 7.2 as supported in the [requirements for Ibexa DXP 4.6](https://doc.ibexa.co/en/latest/getting_started/requirements/#clustering) ### PHP API - Enhanced the PHP API reference with the following new classes for Ibexa DXP 4.6 and 5.0: - [`Ibexa\Contracts\AdminUi\Event\ResolveVersionPreviewUrlEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-AdminUi-Event-ResolveVersionPreviewUrlEvent.html) - [`Ibexa\Contracts\AdminUi\Exception\UnresolvedPreviewUrlException`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-AdminUi-Exception-UnresolvedPreviewUrlException.html) - [`Ibexa\Contracts\AdminUi\PreviewUrlResolver\VersionPreviewUrlResolverInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-AdminUi-PreviewUrlResolver-VersionPreviewUrlResolverInterface.html) - [`Ibexa\Contracts\Core\Validation\Constraint\UniqueIdentifier`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Validation-Constraint-UniqueIdentifier.html) - [`Ibexa\Contracts\Core\Validation\Constraint\UniqueIdentifierValidator`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Validation-Constraint-UniqueIdentifierValidator.html) - [`Ibexa\Contracts\Discounts\Event\EnableDiscountEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-EnableDiscountEvent.html) - [`Ibexa\Contracts\Discounts\Event\BeforeDisableDiscountEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-BeforeDisableDiscountEvent.html) - [`Ibexa\Contracts\Discounts\Event\BeforeEnableDiscountEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-BeforeEnableDiscountEvent.html) - [`Ibexa\Contracts\Discounts\Event\DisableDiscountEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-DisableDiscountEvent.html) - [`Ibexa\Contracts\Discounts\Value\Query\Criterion\IndexedAtCriterion`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-IndexedAtCriterion.html) - [`Ibexa\Contracts\Discounts\Value\Query\Criterion\UpdatedAtCriterion`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-UpdatedAtCriterion.html) - [`Ibexa\Contracts\Messenger\Transport\MessageProviderInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Messenger-Transport-MessageProviderInterface.html) - [`Ibexa\Contracts\ProductCatalog\Values\Product\Query\AttributeCriterionBuilderRegistry`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-AttributeCriterionBuilderRegistry.html) - [`Ibexa\Contracts\ProductCatalog\Values\Product\Query\AttributeCriterionBuilderRegistryInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-AttributeCriterionBuilderRegistryInterface.html) - [`Ibexa\Contracts\ProductCatalog\Values\Product\Query\AttributeCriterionBuilder\AttributeCriterionBuilderInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-AttributeCriterionBuilder-AttributeCriterionBuilderInterface.html) - [`Ibexa\Contracts\ProductCatalog\Values\Product\Query\AttributeCriterionBuilder\CheckboxBuilder`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-AttributeCriterionBuilder-CheckboxBuilder.html) - [`Ibexa\Contracts\ProductCatalog\Values\Product\Query\AttributeCriterionBuilder\ColorBuilder`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-AttributeCriterionBuilder-ColorBuilder.html) - [`Ibexa\Contracts\ProductCatalog\Values\Product\Query\AttributeCriterionBuilder\FloatBuilder`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-AttributeCriterionBuilder-FloatBuilder.html) - [`Ibexa\Contracts\ProductCatalog\Values\Product\Query\AttributeCriterionBuilder\IntegerBuilder`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-AttributeCriterionBuilder-IntegerBuilder.html) - [`Ibexa\Contracts\ProductCatalog\Values\Product\Query\AttributeCriterionBuilder\SelectionBuilder`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-AttributeCriterionBuilder-SelectionBuilder.html) In addition, a new exception is available in the 5.0 version: - [`Ibexa\Contracts\AutomatedTranslation\Exception\ClientNotConfiguredException`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-AutomatedTranslation-Exception-ClientNotConfiguredException.html) ## August 2025 ### Security - Added instructions on [how to generate PEM keypair](https://doc.ibexa.co/en/5.0/infrastructure_and_maintenance/security/development_security/#jwt-authentication) for JWT authentication ### Administration - Updated a list of [bundles available in the product](https://doc.ibexa.co/en/5.0/administration/project_organization/bundles/#core-packages) ### Training - A new version of Ibexa DXP Developer Training has been released, this time focusing on the v5.0.x. ### v5.0.1 - [v5.0.1 release notes](https://doc.ibexa.co/en/5.0/release_notes/ibexa_dxp_v5.0/#ibexa-dxp-v501) ### v4.6.23 - [v4.6.23 release notes](https://doc.ibexa.co/en/4.6/release_notes/ibexa_dxp_v4.6/#ibexa-dxp-v4623) ### v4.6.22 - [v4.6.22 release notes](https://doc.ibexa.co/en/4.6/release_notes/ibexa_dxp_v4.6/#ibexa-dxp-v4622) ### PHP API - Enhanced the PHP API reference with the following new classes: - [`Ibexa\Contracts\ProductCatalogSymbolAttribute\Search\Criterion\SymbolAttribute`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalogSymbolAttribute-Search-Criterion-SymbolAttribute.html) - [`Ibexa\Contracts\ProductCatalogSymbolAttribute\Value\ChecksumInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalogSymbolAttribute-Value-ChecksumInterface.html) - [`Ibexa\Contracts\Cart\Exception\VatCalculationExceptionInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Cart-Exception-VatCalculationExceptionInterface.html) - [`Ibexa\Contracts\Core\Repository\Values\Notification\CriterionHandlerInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Notification-CriterionHandlerInterface.html) - [`Ibexa\Contracts\Core\Repository\Values\Notification\Query\CriterionInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Notification-Query-CriterionInterface.html) - [`Ibexa\Contracts\Core\Repository\Values\Notification\Query\Criterion\DateCreated`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Notification-Query-Criterion-DateCreated.html) - [`Ibexa\Contracts\Core\Repository\Values\Notification\Query\NotificationQuery`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Notification-Query-NotificationQuery.html) - [`Ibexa\Contracts\ProductCatalog\Values\Product\Query\Criterion\AbstractPriceRange`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-Criterion-AbstractPriceRange.html) - [`Ibexa\Contracts\ProductCatalog\Values\Product\Query\Criterion\CustomPriceRange`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-Criterion-CustomPriceRange.html) ## July 2025 ### v5.0.0 [Ibexa DXP v5.0.0](https://doc.ibexa.co/en/5.0/release_notes/ibexa_dxp_v5.0/#ibexa-dxp-v500) is now available, accompanied by [v5.0 documentation](https://doc.ibexa.co/en/5.0/). Ibexa DXP 5.0 LTS benefits of the [Long-Term Support](https://www.ibexa.co/blog/continuous-innovation-how-ibexa-dxp-s-lifecycle-enhances-feature-delivery). See how to [update from 4.6 to 5.0](https://doc.ibexa.co/en/5.0/update_and_migration/from_4.6/update_to_5.0/). #### LTS Updates LTS Updates from v4.6 are included out of the box in v5.0 as features. Installation instructions for v4.6 are not needed in v5.0, or replaced by configuration instructions. | 4.6 | 5.0 | | ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | | [Install AI Actions](https://doc.ibexa.co/en/4.6/ai_actions/install_ai_actions/) | [Configure AI Actions](https://doc.ibexa.co/en/5.0/ai_actions/configure_ai_actions/) | | [Install Discounts](https://doc.ibexa.co/en/4.6/discounts/install_discounts/) | [Customize Discounts](https://doc.ibexa.co/en/5.0/discounts/configure_discounts/) directly | | [Date and time attribute: Installation](https://doc.ibexa.co/en/4.6/pim/attributes/date_and_time/#installation) | [Date and time attribute: Usage](https://doc.ibexa.co/en/5.0/pim/attributes/date_and_time/#usage) | | [Symbol attribute: Installation](https://doc.ibexa.co/en/4.6/pim/attributes/symbol_attribute_type/#installation) | [Symbol attribute](https://doc.ibexa.co/en/5.0/pim/attributes/symbol_attribute_type/) | #### PHP API Documentation examples and [PHP API Reference](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/) have been updated for v5.0. You can use [`ibexa/rector`](https://github.com/ibexa/rector#readme) package that allows to maintain your custom PHP code quality. Consult the [Ibexa DXP v5.0 renames, deprecations and removals](https://doc.ibexa.co/en/5.0/release_notes/ibexa_dxp_v5.0_deprecations/) to learn more about the changes. Major additions to the PHP API Reference are [`Ibexa\Contracts\Collaboration`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/namespaces/ibexa-contracts-collaboration.html) and [`Ibexa\Contracts\Share`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/namespaces/ibexa-contracts-share.html) namespaces, the bulding blocks for the [collaboration framework](https://doc.ibexa.co/en/5.0/release_notes/ibexa_dxp_v5.0/#collaboration). #### REST API Layout for [v5.0 REST API Reference](https://doc.ibexa.co/en/5.0/api/rest_api/rest_api_reference/rest_api_reference.html) is now changed. As [Ibexa DXP 5.0 is OpenAPI compliant](https://doc.ibexa.co/en/5.0/api/rest_api/rest_api_usage/#openapi-support), the specification output is used to generate the online reference. You can also check the documentation directly on your development installations at `/api/ibexa/v2/doc`. ## June 2025 ### Security - Added [warning about code block access](https://doc.ibexa.co/en/4.6/infrastructure_and_maintenance/security/security_checklist/#limit-access-to-code-blocks) ### v4.6.21 - [v4.6.21 release notes](https://doc.ibexa.co/en/4.6/release_notes/ibexa_dxp_v4.6/#ibexa-dxp-v4621) ### Discounts - Introduced new [Discounts](https://doc.ibexa.co/en/4.6/discounts/discounts/) feature that allows online stores to temporarily or permanently reduce prices on specific products or categories. Discounts documentation includes: - [Product guide](https://doc.ibexa.co/en/4.6/discounts/discounts_guide/) - [Installation documentation](https://doc.ibexa.co/en/4.6/discounts/install_discounts/) - [Configuration documentation](https://doc.ibexa.co/en/4.6/discounts/configure_discounts/) - [API usage](https://doc.ibexa.co/en/4.6/discounts/discounts_api/) - New [Search Sort Clauses](https://doc.ibexa.co/en/4.6/search/discounts_search_reference/discounts_sort_clauses/) and [Search Criteria](https://doc.ibexa.co/en/4.6/search/discounts_search_reference/discounts_criteria/) - New [events](https://doc.ibexa.co/en/4.6/api/event_reference/discounts_events/) - Rest API for [Discounts](https://doc.ibexa.co/en/4.6/api/rest_api/rest_api_reference/rest_api_reference.html#discounts) and [Discount Codes](https://doc.ibexa.co/en/4.6/api/rest_api/rest_api_reference/rest_api_reference.html#discount-codes) - New [Twig functions](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/discounts_twig_functions/) and [Twig Components groups](https://doc.ibexa.co/en/4.6/administration/back_office/back_office_elements/custom_components/#discounts) - New [policies](https://doc.ibexa.co/en/4.6/permissions/policies/#discounts) and [limitations](https://doc.ibexa.co/en/4.6/permissions/limitation_reference/#discount-owner-limitation) ### PHP API - Enhanced the PHP API reference with the following new classes: - [`Ibexa\Contracts\Checkout\Exception`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/namespaces/ibexa-contracts-checkout-exception.html) - [`Ibexa\Contracts\Checkout\Exception\CheckoutException`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Checkout-Exception-CheckoutException.html) - [`Ibexa\Contracts\Checkout\Discounts\DiscountsValidationFailedException`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Checkout-Discounts-DiscountsValidationFailedException.html) - [`Ibexa\Contracts\Discounts\Exception\DiscountValueResolutionException`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Exception-DiscountValueResolutionException.html) ## May 2025 ### v4.6.20 - [v4.6.20 release notes](https://doc.ibexa.co/en/4.6/release_notes/ibexa_dxp_v4.6/#ibexa-dxp-v4620) ### Twig Components - Documented new [Twig Components](https://doc.ibexa.co/en/4.6/templating/components/) feature that allows you to effortlessly build customizable and reusable Twig templates in Ibexa DXP ### Extending Sub-items view - Added documentation about how to [add new views or overwrite existing ones in the Sub-items list](https://doc.ibexa.co/en/4.6/administration/back_office/subitems_list/#create-custom-sub-items-list-view) ### PHP API - Enhanced the PHP API with the following new classes: - [`Ibexa\Contracts\AdminUi\Menu\AbstractActionBuilder`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-AdminUi-Menu-AbstractActionBuilder.html) - [`Ibexa\Contracts\TwigComponents\ComponentInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-TwigComponents-ComponentInterface.html) - [`Ibexa\Contracts\TwigComponents\ComponentRegistryInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-TwigComponents-ComponentRegistryInterface.html) - [`Ibexa\Contracts\TwigComponents\Event\RenderGroupEvent`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-TwigComponents-Event-RenderGroupEvent.html) - [`Ibexa\Contracts\TwigComponents\Event\RenderSingleEvent`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-TwigComponents-Event-RenderSingleEvent.html) - [`Ibexa\Contracts\TwigComponents\Exception\InvalidArgumentException`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-TwigComponents-Exception-InvalidArgumentException.html) - [`Ibexa\Contracts\TwigComponents\Renderer\RendererInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-TwigComponents-Renderer-RendererInterface.html) ### Requirements update - Updated [requirements](https://doc.ibexa.co/en/4.6/getting_started/requirements/) for Ibexa DXP 4.6: MySQL 8.4, Node 20 and Node 22 are now officially supported ### AI Actions Connect handler - Documented [how to use the `ibexa:connect:init-custom-property-structures` command](https://doc.ibexa.co/en/4.6/ai_actions/install_ai_actions/#initiate-integration) to integrate [AI actions](https://doc.ibexa.co/en/4.6/ai_actions/ai_actions_guide/) with [Ibexa Connect](https://doc.ibexa.co/projects/connect/en/latest/) ### Contributions We want to thank [todomagichere](https://github.com/todomagichere) and [hgiesenow](https://github.com/hgiesenow) for their help in improving the [Order management API](https://doc.ibexa.co/en/4.6/commerce/order_management/order_management_api) and [Reverse proxy](https://doc.ibexa.co/en/4.6/infrastructure_and_maintenance/cache/http_cache/reverse_proxy) documentation pages ## April 2025 ### Content management - Introduced a [custom DAM connector example](https://doc.ibexa.co/en/latest/content_management/images/add_image_asset_from_dam/#extend-dam-support-by-adding-custom-connector) - Added [grace period definition and configuration](https://doc.ibexa.co/en/latest/administration/configuration/repository_configuration/#grace-period-for-archived-versions) ### AI Actions - Documented [how to couple AI Actions and Ibexa Connect](https://doc.ibexa.co/en/latest/ai_actions/configure_ai_actions/#configure-access-to-ibexa-connect) to build complex data transformation workflows without having to rely on custom code ### REST API - Added [`/cart/{identifier}/summary` REST resource's `ShortCartSummary` response format](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_reference/rest_api_reference.html#managing-commerce-carts-cart-summary) ### Infrastructure and maintenance - Announced [v4.6.19 release notes](https://doc.ibexa.co/en/latest/release_notes/ibexa_dxp_v4.6/#ibexa-dxp-v4619) and [v4.6.19 upgrade instructions](https://doc.ibexa.co/en/latest/update_and_migration/from_4.6/update_from_4.6/#v4619) with an important security notice about RichText XML, and introducing [Ibexa Rector](https://doc.ibexa.co/en/latest/update_and_migration/from_4.6/update_from_4.6/#ibexa-rector) to help to maintain custom code ## March 2025 ### Release notes - Redesigned [Release notes page](https://doc.ibexa.co/en/4.6/release_notes/ibexa_dxp_v4.6/) now includes filters to easily sort by product edition or LTS Update type, while the updated documentation homepage provides quick access to essential details, showcasing changes introduced in the latest patch and LTS Update releases ### Requirements update - Updated [requirements](https://doc.ibexa.co/en/4.6/getting_started/requirements/#operating-system) for Ibexa DXP: RHEL 9.5 and CentOS Stream 9 are now supported for v4.6 ### AI Actions - Specified minimum Ibexa DXP version supported while working with AI Actions - AI Actions product guide: [Availability](https://doc.ibexa.co/en/4.6/ai_actions/ai_actions_guide/#availability) - AI Actions section: [Install AI Actions](https://doc.ibexa.co/en/4.6/ai_actions/install_ai_actions/) ### Online Editor - Added an example in the Online Editor documentation showing how to [add characters and shortcuts for specific characters to the SpecialCharacters plugin in CKEditor configuration](https://doc.ibexa.co/en/4.6/content_management/rich_text/extend_online_editor/#change-ckeditor-configuration) ### Templating - Updated a description of the [`ibexa_render`](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/content_twig_functions/#content-rendering) Twig function to mention its support for objects implementing the [`ContentAwareInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-ContentAwareInterface.html) as argument ## February 2025 ### Security - Expanded security recommendations to follow when working with [images submitted by users](https://doc.ibexa.co/en/4.6/content_management/images/images/#configuring-image-variations) ### Date and time attributes - Added documentation for the latest LTS Update: [Date and time attributes](https://doc.ibexa.co/en/4.6/pim/attributes/date_and_time/) ### Automated Translation - Added information about how you can install and extend the [Automated Translation](https://doc.ibexa.co/en/4.6/multisite/languages/automated_translations/) feature ### Interactive demos - Updated [Form Builder](https://doc.ibexa.co/en/4.6/content_management/forms/form_builder_guide/#how-does-form-builder-work), [Page Builder](https://doc.ibexa.co/en/4.6/content_management/pages/page_builder_guide/#create-page), and [Online Editor](https://doc.ibexa.co/en/4.6/content_management/rich_text/online_editor_guide/) product guides by adding interactive demos that present these features ### Page Builder clipboard - Described how you can use the [Page Builder's clipboard](https://doc.ibexa.co/projects/userguide/en/latest/content_management/create_edit_pages/#copy-blocks) to copy blocks between pages ### REST API - Described endpoints for [Segment](https://doc.ibexa.co/en/4.6/api/rest_api/rest_api_reference/rest_api_reference.html#segments) and [Segment Group](https://doc.ibexa.co/en/4.6/api/rest_api/rest_api_reference/rest_api_reference.html#segment-groups) management - Described endpoints for [AI Action Configurations](https://doc.ibexa.co/en/4.6/api/rest_api/rest_api_reference/rest_api_reference.html#ai-actions-list-action-configurations) and [AI Action Types](https://doc.ibexa.co/en/4.6/api/rest_api/rest_api_reference/rest_api_reference.html#ai-actions-list-action-types) - Improved the example for [creating Orders](https://doc.ibexa.co/en/4.6/api/rest_api/rest_api_reference/rest_api_reference.html#orders-create-order), to show how to pass shipping cost data ### HTTP Cache - Improved the VCL snippet to cache the first ESI request when [using Basic Auth with Fastly](https://doc.ibexa.co/en/4.6/infrastructure_and_maintenance/cache/http_cache/fastly/#enable-basic-auth-on-fastly) ### Search - Expanded the lists of [search criteria](https://doc.ibexa.co/en/4.6/search/criteria_reference/search_criteria_reference/) and [sort clauses](https://doc.ibexa.co/en/4.6/search/sort_clause_reference/sort_clause_reference/) to show their support for [searching in Trash](https://doc.ibexa.co/en/4.6/search/search_in_trash_reference/) ### Templating - Added the [icon reference](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/icon_twig_functions/#icons-reference) that lists all the icons you can use when extending the back office - Updated descriptions of the following Twig functions to mention their support for objects implementing the [`ContentAwareInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-ContentAwareInterface.html) as arguments: - [`ibexa_content_field_identifier_first_filled_image`](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/image_twig_functions/#ibexa_content_field_identifier_first_filled_image) - [`ibexa_content_name`](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/content_twig_functions/#ibexa_content_name) - [`ibexa_field_is_empty`](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/field_twig_functions/#ibexa_field_is_empty) - [`ibexa_field_description`](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/field_twig_functions/#ibexa_field_description) - [`ibexa_field_name`](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/field_twig_functions/#ibexa_field_name) - [`ibexa_field_value`](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/field_twig_functions/#ibexa_field_value) - [`ibexa_field`](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/field_twig_functions/#ibexa_field) - [`ibexa_has_field`](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/field_twig_functions/#ibexa_has_field) - [`ibexa_render_field`](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/field_twig_functions/#field-rendering) - [`ibexa_seo_is_empty`](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/content_twig_functions/#ibexa_content_name) - [`ibexa_seo`](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/content_twig_functions/#ibexa_seo_is_empty) - [`ibexa_taxonomy_entries_for_content`](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/content_twig_functions/#ibexa_seo) - Described new Twig filter for product attributes grouping: [`ibexa_product_catalog_group_attributes`](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/product_twig_functions/#ibexa_product_catalog_group_attributes) ### DDEV - Described how you can use the [Ibexa Cloud addons](https://doc.ibexa.co/en/4.6/ibexa_cloud/ddev_and_ibexa_cloud/#with-ibexa-cloud-add-ons) when working with Ibexa Cloud projects ### Ibexa Cloud - Described how to [set up Composer authentication](https://doc.ibexa.co/en/4.6/ibexa_cloud/install_on_ibexa_cloud/#composer-authentication-using-the-web-console) when creating an Ibexa Cloud project #### PHP API Enhanced the PHP API with the following new classes and interfaces: - `Ibexa\Contracts\Cart`: - [`Value\Query\Criterion\LogicalAnd`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Cart-Value-Query-Criterion-LogicalAnd.html) - [`Value\Query\Criterion\OwnerCriterion`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Cart-Value-Query-Criterion-OwnerCriterion.html) - [`Value\Query\CriterionInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Cart-Value-Query-CriterionInterface.html) - `Ibexa\Contracts\Segmentation`: - [`Exception\ValidationFailedExceptionInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Segmentation-Exception-ValidationFailedExceptionInterface.html) - `Ibexa\Contracts\ProductCatalog`: - [`Iterator\BatchIteratorAdapter\RegionFetchAdapter`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Iterator-BatchIteratorAdapter-RegionFetchAdapter.html) - `Ibexa\Contracts\Connect`: - [`ConnectClientInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-ConnectClientInterface.html) - [`Exception\BadResponseException`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Exception-BadResponseException.html) - [`Exception\UnserializablePayload`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Exception-UnserializablePayload.html) - [`Exception\UnserializableResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Exception-UnserializableResponse.html) - [`PaginationInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-PaginationInterface.html) - [`Resource\DataStructure\DataStructureBuilder`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-DataStructure-DataStructureBuilder.html) - [`Resource\DataStructure\DataStructureCreateStruct`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-DataStructure-DataStructureCreateStruct.html) - [`Resource\DataStructure\DataStructureFilter`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-DataStructure-DataStructureFilter.html) - [`Resource\DataStructure\DataStructureProperty`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-DataStructure-DataStructureProperty.html) - [`Resource\DataStructure\DataStructurePropertyType`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-DataStructure-DataStructurePropertyType.html) - [`Resource\DataStructureInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-DataStructureInterface.html) - [`Resource\Hook\HookCreateStruct`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-Hook-HookCreateStruct.html) - [`Resource\Hook\HookFilter`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-Hook-HookFilter.html) - [`Resource\Hook\HookSetDetailsStruct`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-Hook-HookSetDetailsStruct.html) - [`Resource\HookInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-HookInterface.html) - [`Resource\Scenario\ScenarioCreateStruct`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-Scenario-ScenarioCreateStruct.html) - [`Resource\Scenario\ScenarioFilter`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-Scenario-ScenarioFilter.html) - [`Resource\ScenarioInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-ScenarioInterface.html) - [`Resource\Team\TeamVariableCreateStruct`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-Team-TeamVariableCreateStruct.html) - [`Resource\Team\TeamVariableFilter`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-Team-TeamVariableFilter.html) - [`Resource\Team\TeamVariableUpdateStruct`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-Team-TeamVariableUpdateStruct.html) - [`Resource\TeamInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-TeamInterface.html) - [`Resource\Template\TemplateCreateStruct`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-Template-TemplateCreateStruct.html) - [`Resource\Template\TemplateFilter`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-Template-TemplateFilter.html) - [`Resource\TemplateInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-TemplateInterface.html) - [`Response\DataStructure\CreateResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-DataStructure-CreateResponse.html) - [`Response\DataStructure\ListResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-DataStructure-ListResponse.html) - [`Response\DataStructure\RetrieveResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-DataStructure-RetrieveResponse.html) - [`Response\Hook\CreateResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-Hook-CreateResponse.html) - [`Response\Hook\ListResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-Hook-ListResponse.html) - [`Response\Hook\RetrieveResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-Hook-RetrieveResponse.html) - [`Response\Hook\SetDetailsResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-Hook-SetDetailsResponse.html) - [`Response\Scenario\CreateResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-Scenario-CreateResponse.html) - [`Response\Scenario\ListResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-Scenario-ListResponse.html) - [`Response\Scenario\RetrieveResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-Scenario-RetrieveResponse.html) - [`Response\Team\TeamVariableCreateResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-Team-TeamVariableCreateResponse.html) - [`Response\Team\TeamVariableListResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-Team-TeamVariableListResponse.html) - [`Response\Team\TeamVariableRetrieveResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-Team-TeamVariableRetrieveResponse.html) - [`Response\Team\TeamVariableUpdateResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-Team-TeamVariableUpdateResponse.html) - [`Response\Template\BlueprintResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-Template-BlueprintResponse.html) - [`Response\Template\CreateResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-Template-CreateResponse.html) - [`Response\Template\ListResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-Template-ListResponse.html) - [`Response\Template\RetrieveResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-Template-RetrieveResponse.html) - [`ResponseInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-ResponseInterface.html) - [`TransportInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-TransportInterface.html) - [`Value\Blueprint\Flow`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Value-Blueprint-Flow.html) - [`Value\Blueprint\Metadata\Scenario`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Value-Blueprint-Metadata-Scenario.html) - [`Value\Blueprint\Metadata`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Value-Blueprint-Metadata.html) - [`Value\Blueprint\Module\CustomWebhook`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Value-Blueprint-Module-CustomWebhook.html) - [`Value\Blueprint\Module\JsonCreate`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Value-Blueprint-Module-JsonCreate.html) - [`Value\Blueprint\Module\ModuleDesigner`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Value-Blueprint-Module-ModuleDesigner.html) - [`Value\Blueprint\Module\WebhookRespond`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Value-Blueprint-Module-WebhookRespond.html) - [`Value\Blueprint`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Value-Blueprint.html) - [`Value\Controller`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Value-Controller.html) - [`Value\Scheduling`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Value-Scheduling.html) ## January 2025 ### Trainings - The Content Editor Training has been released. Learn more in the [annoucement blogpost](https://www.ibexa.co/blog/constant-development-is-key-so-here-s-a-new-training-for-content-editors) ### Infrastructure and maintenance - The upgrade instructions from v3.3 to v4.6 have been expanded with a section describing the [GraphQL changes in v4](https://doc.ibexa.co/en/4.6/update_and_migration/from_3.3/to_4.0/#graphql) - Ubuntu 24.04 has been added to the [list of officially supported operating systems](https://doc.ibexa.co/en/4.6/getting_started/requirements/#operating-system) ### PHP API - Added the following interfaces and classes to the public PHP API: - [`Ibexa\Contracts\AdminUi\Permission\PermissionCheckContextProviderInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-AdminUi-Permission-PermissionCheckContextProviderInterface.html) - [`Ibexa\Contracts\AdminUi\Values\PermissionCheckContext`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-AdminUi-Values-PermissionCheckContext.html) - [`Ibexa\Contracts\Checkout\Discounts\DataMapper\DiscountsDataMapperInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Checkout-Discounts-DataMapper-DiscountsDataMapperInterface.html) - [`Ibexa\Contracts\Seo\Resolver\FieldValueResolverInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Seo-Resolver-FieldValueResolverInterface.html) ## December 2024 ### Infrastructure and maintenance - Added [v4.6.14 to v4.6.15 update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.6/update_from_4.6/#v4615) ## AI Actions - Added [extending AI Actions](https://doc.ibexa.co/en/latest/ai_actions/extend_ai_actions/index.md) documentation ### Security - Expanded the [Security checklist](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/security/security_checklist/index.md) with advice on TLS, HSTS, DNSSEC, CAA, and domain update protection ### PHP API - Added the following interfaces to the public PHP API: - [`Ibexa\Contracts\ProductCatalog\Values\Price\PriceEnvelopeInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Price-PriceEnvelopeInterface.html) - [`Ibexa\Contracts\ProductCatalog\Values\Price\PriceStampInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Price-PriceStampInterface.html) - [`Ibexa\Contracts\ProductCatalog\Values\StampInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-StampInterface.html) ## November 2024 ### Infrastructure and maintenance - Added [v4.6.13 to v4.6.14 update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.6/update_from_4.6/#v4614) which include security fixes - Added [v3.3.40 to v3.3.41 update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_3.3/update_from_3.3/#v3341) which include security fixes ### Content management - Added [AI Actions documentation](https://doc.ibexa.co/en/4.6/ai_actions/ai_actions/) ### Search - New [`IsBookmarked` location criterion](https://doc.ibexa.co/en/4.6/search/criteria_reference/isbookmarked_criterion/) - [`IsUserEnabled` is now available on Solr and Elastisearch](https://doc.ibexa.co/en/4.6/search/criteria_reference/isuserenabled_criterion/) ### Documentation - When you search using the top bar, if there are more than the 10 listed results, you can see a link to a page with further results at the bottom of the drop-down suggestion list ### PHP API - Added the following namespaces, interfaces, and classes to the public PHP API: - [`Ibexa\Contracts\Core\Validation`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/namespaces/ibexa-contracts-core-validation.html) namespace and its descendants - [`Ibexa\Contracts\Notifications\SystemNotification`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/namespaces/ibexa-contracts-notifications-systemnotification.html) namespace and its descendants - [`Ibexa\Contracts\Notifications\Value\Recipent\UserRecipientInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Notifications-Value-Recipent-UserRecipientInterface.html) - [`Ibexa\Contracts\ProductCatalog\Values\Product\Query\Criterion\UpdatedAt`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-Criterion-UpdatedAt.html) - [`Ibexa\Contracts\ProductCatalog\Values\Product\Query\Criterion\UpdatedAtRange`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-Criterion-UpdatedAtRange.html) - [`Ibexa\Contracts\ProductCatalog\ProductReferencesResolverStrategy`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-ProductReferencesResolverStrategy.html) ## October 2024 ### Content management - Added a caution at the end of the [Create custom Page block](https://doc.ibexa.co/en/4.6/content_management/pages/create_custom_page_block/#add-edit-template) article - Added `add_block_to_available_blocks` to a [list of available data migration actions](https://doc.ibexa.co/en/4.6/content_management/data_migration/data_migration_actions/#available-migration-actions) ### Infrastructure and maintenance - Updated the [reverse proxy configuration instructions](https://doc.ibexa.co/en/4.6/infrastructure_and_maintenance/cache/http_cache/reverse_proxy/#varnish-and-basic-auth) by mentioning Basic Auth setup for Varnish - Reorganized the [Updating Ibexa DXP](https://doc.ibexa.co/en/4.6/update_and_migration/update_ibexa_dxp/) section to put information in logical order and remove duplicates - [Added v4.6.11 to v4.6.12 update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.6/update_from_4.6/#v4612) - [Added v4.6.12 to v4.6.13 update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.6/update_from_4.6/#v4613) mentioning a command to clean up duplicated entries in the `ezcontentobject_attribute` table - Changed the [Update to v3.3](https://doc.ibexa.co/en/4.6/update_and_migration/from_2.5/to_3.3/#b-update-the-app) instructions to help avoid an error at `composer update` stage - Changed the instructions by adding a requirement to handle the Ibexa Cloud configuration: - [Update to v4.0](https://doc.ibexa.co/en/4.6/update_and_migration/from_3.3/to_4.0/#ibexa-cloud) - [Update to v3.3.latest](https://doc.ibexa.co/en/4.6/update_and_migration/from_3.3/update_from_3.3/#v3313) - Added a suggestion to [remove obsolete database tables](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.3/update_from_4.3_new_commerce/#update-the-database) that were used by a legacy Commerce package ## Personalization - Added dynamic attribute submodels information by: - mentioning them in [customizing the recommendation request](https://doc.ibexa.co/en/4.6/personalization/api_reference/recommendation_api/#customizing-the-recommendation-request) instructions - describing them in [user documentation](https://doc.ibexa.co/projects/userguide/en/latest/personalization/recommendation_models/#dynamic-attributes) - Added time-slot based models information by: - changing the list of parameters available when [customizing the recommendation request](https://doc.ibexa.co/en/4.6/personalization/api_reference/recommendation_api/#customizing-the-recommendation-request) - describing them in [user documentation](https://doc.ibexa.co/projects/userguide/en/latest/personalization/recommendation_models/#time-slot-based-models) - Updated configuration details (including endpoint addresses and code examples) in multiple how-to articles: - [Enable Personalization](https://doc.ibexa.co/en/4.6/personalization/enable_personalization/) - [Integrate recommendation service](https://doc.ibexa.co/en/4.6/personalization/integrate_recommendation_service/) - [Tracking integration](https://doc.ibexa.co/en/4.6/personalization/tracking_integration/) - [Track events with ibexa-tracker.js](https://doc.ibexa.co/en/4.6/personalization/tracking_with_ibexa-tracker/) ### PIM - Updated the [Product API](https://doc.ibexa.co/en/4.6/pim/product_api/) article by fixing method signatures and adding links to the PHP API reference ### PHP API - Added the following new classes to the public PHP API: - [Ibexa\\Contracts\\AdminUi\\Menu](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/namespaces/ibexa-contracts-adminui-menu.html) - [Ibexa\\Contracts\\Core\\Pool](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/namespaces/ibexa-contracts-core-pool.html) - [Ibexa\\Contracts\\CoreSearch\\Values\\Query](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/namespaces/ibexa-contracts-coresearch-values-query.html) - [Ibexa\\Contracts\\ProductCatalog\\Local\\Attribute\\ContextAwareValueValidatorInterface](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Local-Attribute-ContextAwareValueValidatorInterface.html) ### REST API - Updated the [REST API authentication](https://doc.ibexa.co/en/4.6/api/rest_api/rest_api_authentication/) instructions ## September 2024 ### Getting started - Updated product version requirements and database connection configuration instructions in [Install Ibexa DXP with DDEV](https://doc.ibexa.co/en/4.6/getting_started/install_with_ddev/#2-configure-ddev) ### Infrastructure and maintenance - Modified v4.5.x to v4.6 update instructions by adding [Update Solr configuration](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.5/update_from_4.5/#update-solr-configuration) section - Added [v4.6.8 to v4.6.11 update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.6/update_from_4.6/#v4611) ### PHP API - Added edition information to [PHP API reference](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/) to aid navigation ### REST API - Removed multiple obsolete RAML types from the [REST API reference](https://doc.ibexa.co/en/4.6/api/rest_api/rest_api_reference/rest_api_reference.html) ### User management - Updated the OAuth server [installation instructions](https://doc.ibexa.co/en/4.6/users/oauth_server/#server-installation) ## August 2024 ### Product guides - [Ibexa Experience product guide](https://doc.ibexa.co/en/4.6/ibexa_products/ibexa_experience/) - [Ibexa Commerce product guide](https://doc.ibexa.co/en/4.6/ibexa_products/ibexa_commerce/) - Added [page collecting all feature product guides](https://doc.ibexa.co/en/4.6/product_guides/product_guides/) ### Content management - Added how to [hide a taxonomy menu item](https://doc.ibexa.co/en/4.6/content_management/taxonomy/taxonomy/#hide-menu-item) ### Data migration - Added a note about [multi-repository and dynamic migration folders](https://doc.ibexa.co/en/4.6/content_management/data_migration/managing_migrations/#migration-folders) ## July 2024 ### Getting started - Added instructions in [Install Ibexa DXP](https://doc.ibexa.co/en/4.6/getting_started/install_ibexa_dxp/#create-project) about using PHP 8.3 to create a project - Updated the [requirements for running v3.3.x on PHP 8.3](https://doc.ibexa.co/en/4.6/getting_started/requirements/#php) ### Infrastructure and maintenance - Added [v4.6.4 to v4.6.8 update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.5/update_from_4.5/#v468) - Modified [v3.3.x to v3.3.latest update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_3.3/update_from_3.3/#update-the-application) - Updated the recommendations in [Performance](https://doc.ibexa.co/en/4.6/infrastructure_and_maintenance/performance/#symfony) by mentioning Symfony ### Data migration - Added a note about multi-repository scenario in [Managing migration](https://doc.ibexa.co/en/4.6/content_management/data_migration/managing_migrations/#migration-folders) - Updated the instructions for [Importing data](https://doc.ibexa.co/en/4.6/content_management/data_migration/importing_data/#built-in-functions) by mentioning the `env` function and a possibility of swapping content items assigned to a location ### Ibexa Cloud - Placed all articles about Ibexa Cloud [in a common location](https://doc.ibexa.co/en/4.6/ibexa_cloud/ibexa_cloud/) ### Ibexa Engage - [Added a landing page in the Ibexa Engage area](https://doc.ibexa.co/en/4.6/ibexa_engage/ibexa_engage/) ### Product guides - [Ibexa Cloud product guide](https://doc.ibexa.co/en/4.6/ibexa_cloud/ibexa_cloud_guide/) ## June 2024 ### Ibexa Engage - [Learn more about Ibexa Engage](https://doc.ibexa.co/en/4.6/ibexa_engage/install_ibexa_engage/) ### Search - [Configuring Elasticsearch with analyzers for different languages](https://doc.ibexa.co/en/4.6/search/search_engines/elasticsearch/configure_elasticsearch/#add-language-specific-analysers) - [ContentName search criterion](https://doc.ibexa.co/en/4.6/search/criteria_reference/contentname_criterion/) ### Dashboard - [Customizing the content type for Dashboard container](https://doc.ibexa.co/en/4.6/administration/dashboard/configure_default_dashboard/#container-content-type-identifier) ### Infrastructure and maintenance - [Updated Ibexa Cloud domain to ibexa.cloud](https://doc.ibexa.co/en/4.6/getting_started/install_on_ibexa_cloud/#4-push-the-project) - [v4.6.3 to v4.6.4 update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.5/update_from_4.5/#v464) ### Documentation - A "new" pill now appears in the table of content alongside pages which have been recently created, or have recent important updates or additions ## May 2024 ### PHP API - [PHP API Reference](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/) ### Users - [Warning about recent activity log and user privacy](https://doc.ibexa.co/en/4.6/administration/recent_activity/recent_activity/#user-privacy) ## April 2024 ### Product guides - [Ibexa CDP product guide](https://doc.ibexa.co/en/4.6/cdp/cdp_guide/) ### Infrastructure and maintenance - [v4.6.0 to v4.6.3 update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.5/update_from_4.5/#v463) ### Users - [Recent activity](https://doc.ibexa.co/en/4.6/administration/recent_activity/recent_activity/) - [OAuth server](https://doc.ibexa.co/en/4.6/users/oauth_server/) - Updated [OAuth client](https://doc.ibexa.co/en/4.6/users/oauth_client/) ### Back office - [Customize back office search suggestions](https://doc.ibexa.co/en/4.6/administration/back_office/customize_search_suggestion/) - [Customize back office search result sorting](https://doc.ibexa.co/en/4.6/administration/back_office/customize_search_sorting/) ### Templating - [Site context Twig function `ibexa_site_context_aware`](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/site_context_twig_functions/#ibexa_site_context_aware) - [Storefront Twig function `ibexa_get_anonymous_user_id`](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/storefront_twig_functions/#ibexa_get_anonymous_user_id) ## March 2024 ### Permissions - Updated [Custom policies](https://doc.ibexa.co/en/4.6/permissions/custom_policies/) article ### Content management - Updated [BinaryFile field type](https://doc.ibexa.co/en/4.6/content_management/field_types/field_type_reference/binaryfilefield/) description ### Commerce - Description of [integration with Payum](https://doc.ibexa.co/en/4.6/commerce/payment/payum_integration/) and payment processing gateways ### Search - Updated [Elasticsearch search engine](https://doc.ibexa.co/en/4.6/search/search_engines/elasticsearch/elasticsearch_overview/) description - New Search Criteria: - [Image](https://doc.ibexa.co/en/4.6/search/criteria_reference/image_criterion/) - [ImageDimensions](https://doc.ibexa.co/en/4.6/search/criteria_reference/imagedimensions_criterion/) - [ImageFileSize](https://doc.ibexa.co/en/4.6/search/criteria_reference/imagefilesize_criterion/) - [ImageHeight](https://doc.ibexa.co/en/4.6/search/criteria_reference/imageheight_criterion/) - [ImageMimeType](https://doc.ibexa.co/en/4.6/search/criteria_reference/imagemimetype_criterion/) - [ImageOrientation](https://doc.ibexa.co/en/4.6/search/criteria_reference/imageorientation_criterion/) - [ImageWidth](https://doc.ibexa.co/en/4.6/search/criteria_reference/imagewidth_criterion/) ## February 2024 ### Dashboard - New dashboard sections in User Documentation: - [Dashboard](https://doc.ibexa.co/projects/userguide/en/latest/getting_started/dashboard/dashboard/) - [Work with dashboard](https://doc.ibexa.co/projects/userguide/en/latest/getting_started/dashboard/work_with_dashboard/) - [Dashboard block reference](https://doc.ibexa.co/projects/userguide/en/latest/getting_started/dashboard/dashboard_block_reference/) - Dashboard section in Developer Documentation: - [Configure default dashboard](https://doc.ibexa.co/en/4.6/administration/dashboard/configure_default_dashboard/) - [Customize dashboard](https://doc.ibexa.co/en/4.6/administration/dashboard/customize_dashboard/) - [PHP API Dashboard service](https://doc.ibexa.co/en/4.6/administration/dashboard/php_api_dashboard_service/) ### DAM - [Ibexa DAM](https://doc.ibexa.co/projects/userguide/en/latest/dam/ibexa_dam/) ### PIM - [Price engine REST API](https://doc.ibexa.co/en/4.6/api/rest_api/rest_api_reference/rest_api_reference.html#product-catalog-load-the-list-of-product-prices) ### REST API - [Shipment REST API](https://doc.ibexa.co/en/4.6/api/rest_api/rest_api_reference/rest_api_reference.html#shipments) ### Others - [Updated Create custom view matcher article](https://doc.ibexa.co/en/4.6/templating/templates/create_custom_view_matcher/) - [Actito transactional email integration](https://doc.ibexa.co/en/4.6/commerce/transactional_emails/transactional_emails/#configure-actito-integration) - [Described user profile](https://doc.ibexa.co/projects/userguide/en/latest/getting_started/get_started/#edit-user-profile) ## January 2024 ### Administration - [Enhanced data migration doc](https://doc.ibexa.co/en/4.6/content_management/data_migration/importing_data/) - [Enhanced update to v3.3 procedure](https://doc.ibexa.co/en/4.6/update_and_migration/from_2.5/to_3.3/) ### Content management - New sections in taxonomy documentation: - [How to hide the delete button in large subtrees](https://doc.ibexa.co/en/4.6/content_management/taxonomy/taxonomy/#hide-delete-button-on-large-subtree) - [How to remove orphaned content items](https://doc.ibexa.co/en/4.6/content_management/taxonomy/taxonomy/#remove-orphaned-content-items) - Updated information in User Documentation: - [Enhanced create and edit pages article](https://doc.ibexa.co/projects/userguide/en/latest/content_management/create_edit_pages/) - [Edit embedded content items](https://doc.ibexa.co/projects/userguide/en/latest/content_management/create_edit_content_items/#edit-embedded-content-items) ### DAM - [Ibexa DAM](https://doc.ibexa.co/projects/userguide/en/latest/dam/ibexa_dam/) ### Getting started - [Ibexa Headless product guide](https://doc.ibexa.co/en/4.6/ibexa_products/headless/) - [Enhanced get started article](https://doc.ibexa.co/projects/userguide/en/latest/getting_started/get_started/#edit-user-profile) in User Documentation ### Image management - [Upload and store images](https://doc.ibexa.co/projects/userguide/en/latest/image_management/upload_images/) - [Moved Edit images from Content management](https://doc.ibexa.co/projects/userguide/en/latest/image_management/edit_images/) ### Personalization - [Customize recommendation request with segment parameters](https://doc.ibexa.co/en/4.6/personalization/api_reference/recommendation_api/#segment-parameters) ### PIM - Product search Aggregations: - [BasePriceStatsAggregation](https://doc.ibexa.co/en/4.6/search/aggregation_reference/basepricestats_aggregation/) - [CustomPriceStatsAggregation](https://doc.ibexa.co/en/4.6/search/aggregation_reference/custompricestats_aggregation/) ## December 2023 ### Content management - [Segmentation events](https://doc.ibexa.co/en/4.6/api/event_reference/segmentation_events/) - [Checkbox page block attribute type](https://doc.ibexa.co/en/4.6/content_management/pages/page_block_attributes/#block-attribute-types) - [Updated Create Form Builder Form attribute procedure](https://doc.ibexa.co/en/4.6/content_management/forms/create_form_attribute/#create-form-builder-form-attribute) ### PIM - [Reorganized and updated information in User Documentation](https://doc.ibexa.co/projects/userguide/en/latest/pim/pim/) ### Templating - [Taxonomy view matchers](https://doc.ibexa.co/en/4.6/templating/templates/view_matcher_reference/#taxonomy-entry-id) - [Get content category Twig filter](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/other_twig_filters/#ibexa_taxonomy_entries_for_content) - [Updated arguments list for `ibexa_render()` method](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/content_twig_functions/#ibexa_render) - [New Field information Twig functions](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/field_twig_functions/#ibexa_field_group_name) - [Updated get user Twig functions](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/user_twig_functions/) ### User management - [Reorganized information in the User Management area](https://doc.ibexa.co/en/4.6/users/users/) ## November 2023 ### Commerce - [Option to handle multiple checkout workflows](https://doc.ibexa.co/en/4.6/commerce/checkout/customize_checkout/#manage-multiple-workflows) ### CDP - [CDP activation](https://doc.ibexa.co/en/4.6/cdp/cdp_activation/cdp_activation/) ### Product guides - [Page Builder product guide](https://doc.ibexa.co/en/4.6/content_management/pages/page_builder_guide/) ### Infrastructure and maintenance - [Updated enable Symfony Reverse Proxy](https://doc.ibexa.co/en/4.6/infrastructure_and_maintenance/cache/http_cache/reverse_proxy/#using-symfony-reverse-proxy) ### Others - [Redesigned requirements page](https://doc.ibexa.co/en/4.6/getting_started/requirements/) - [Updated Ibexa Cloud CLI](https://doc.ibexa.co/en/4.6/getting_started/install_on_ibexa_cloud/) - [Updated React app block procedure](https://doc.ibexa.co/en/4.6/content_management/pages/react_app_block/) - [Added fulltext features in search](https://doc.ibexa.co/en/4.6/search/criteria_reference/fulltext_criterion/#supported-syntax) ## October 2023 ### Commerce - [Adding context data to cart](https://doc.ibexa.co/en/4.6/commerce/cart/cart_api/#adding-context-data-to-cart) ### Personalization - [Post visit and price drop triggers](https://doc.ibexa.co/projects/userguide/en/latest/personalization/triggers/#trigger-types) - [Wishlist and Deletefromwishlist events](https://doc.ibexa.co/en/4.6/personalization/api_reference/tracking_api/#track-events) ### PIM - [VAT category configuration update](https://doc.ibexa.co/en/4.6/pim/pim_configuration/#vat-rates) - [Payment Method Name Criterion](https://doc.ibexa.co/en/4.6/search/criteria_reference/payment_method_name_criterion/) ### Product guides - [User management product guide](https://doc.ibexa.co/en/4.6/users/user_management_guide/) ### Migration - [Enhance data migration doc](https://doc.ibexa.co/en/4.6/content_management/data_migration/importing_data/) - [Images migration example](https://doc.ibexa.co/en/4.6/content_management/data_migration/importing_data/#images) - [Expression language functions](https://doc.ibexa.co/en/4.6/content_management/data_migration/importing_data/#built-in-functions) ## September 2023 ### Commerce - Cart - [Merge carts API](https://doc.ibexa.co/en/4.6/commerce/cart/cart_api/#merge-carts) - Checkout - [Reorder](https://doc.ibexa.co/en/4.6/commerce/checkout/reorder/) - [Hide checkout step](https://doc.ibexa.co/en/4.6/commerce/checkout/customize_checkout/#hide-checkout-step) - Order management - [Define cancel order](https://doc.ibexa.co/en/4.6/commerce/order_management/configure_order_management/#define-cancel-order) ### Personalization - [Updated configuration for triggers](https://doc.ibexa.co/en/4.6/personalization/api_reference/tracking_api/#tracking-events-based-on-recommendations) - [Send messages with recommendations](https://doc.ibexa.co/en/4.6/personalization/integrate_recommendation_service/#send-messages-with-recommendations) - [Email triggers](https://doc.ibexa.co/projects/userguide/en/latest/personalization/triggers/) in User Documentation ### PIM - [Product availability Twig extension](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/product_twig_functions/#ibexa_has_product_availability) - [PriceQuery with its criteria](https://doc.ibexa.co/en/4.6/search/criteria_reference/price_search_criteria/) - [Price API](https://doc.ibexa.co/en/4.6/pim/price_api/#prices) ### REST API - Added GET endpoint for all available [Sales Representatives Users](https://doc.ibexa.co/en/4.6/api/rest_api/rest_api_reference/rest_api_reference.html#corporate-account-read-list-of-sales-representatives) ### Storefront - [Display language name instead of its code in language swticher](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/storefront_twig_functions/#ibexa_storefront_get_language_name_by_code) ### Templating - [Render content in PHP](https://doc.ibexa.co/en/4.6/templating/render_content/render_content_in_php/) ### Others - Product guides integrated into Developer Documentation - [Content management](https://doc.ibexa.co/en/4.6/content_management/content_management_guide/) - [Customer portal](https://doc.ibexa.co/en/4.6/customer_management/customer_portal/) - [Form Builder](https://doc.ibexa.co/en/4.6/content_management/forms/form_builder_guide/) - [Online editor](https://doc.ibexa.co/en/4.6/content_management/rich_text/online_editor_guide/) - [Personalization](https://doc.ibexa.co/en/4.6/personalization/personalization_guide/) - [PIM](https://doc.ibexa.co/en/4.6/pim/pim_guide/) - [Updated bundles list](https://doc.ibexa.co/en/4.6/administration/project_organization/bundles/) ## August 2023 ### New home page - Redesigned [home page for User Documentation](https://doc.ibexa.co/projects/userguide/en/latest/) ### Administration - [Install Ibexa DXP with DDEV](https://doc.ibexa.co/en/4.6/getting_started/install_with_ddev/) - [Update from v3.3.x to v3.3.latest](https://doc.ibexa.co/en/4.6/update_and_migration/from_3.3/update_from_3.3/) ### Commerce - [Importing data](https://doc.ibexa.co/en/4.6/content_management/data_migration/importing_data/#commerce) - Cart - [Quick order](https://doc.ibexa.co/en/4.6/commerce/cart/quick_order/) - Checkout - [Create custom strategy](https://doc.ibexa.co/en/4.6/commerce/checkout/customize_checkout/#create-custom-strategy) - Payments - [Implement payment method filtering](https://doc.ibexa.co/en/4.6/commerce/payment/payment_method_filtering/) - [Filter payment methods](https://doc.ibexa.co/projects/userguide/en/latest/commerce/payment/work_with_payment_methods/#filter-payment-methods) - Shipping - [Extend shipping](https://doc.ibexa.co/en/4.6/commerce/shipping_management/extend_shipping/) - [Filter shipping methods](https://doc.ibexa.co/projects/userguide/en/latest/commerce/shipping_management/work_with_shipping_methods/#filter-shipping-methods) ### Online Editor - [Add CKEditor plugins](https://doc.ibexa.co/en/4.6/content_management/rich_text/extend_online_editor/#add-ckeditor-plugins) ### PIM - [Custom name schema strategy](https://doc.ibexa.co/en/4.6/pim/create_custom_name_schema_strategy/) - [IsVirtual Search Criterion](https://doc.ibexa.co/en/4.6/search/criteria_reference/isvirtual_criterion/) ### Security - [Hidden state clarification](https://doc.ibexa.co/en/4.6/infrastructure_and_maintenance/security/security_checklist/#do-not-use-hide-for-read-access-restriction) - [Add timeouts information](https://doc.ibexa.co/en/4.6/infrastructure_and_maintenance/security/security_checklist/#protect-against-brute-force-attacks) ## July 2023 ### v4.5.1 - [v4.5.1 release notes](https://doc.ibexa.co/en/4.6/release_notes/ibexa_dxp_v4.5/#v451) ### New home page - Redesigned [home page for Developer Documentation](https://doc.ibexa.co/en/4.6/) ### Getting started - New cautions in [Install on Ibexa Cloud](https://doc.ibexa.co/en/4.6/getting_started/install_on_ibexa_cloud/) about using `cloud.ibexa.co` instead of `platform.sh` ### Content management - New Page block [Ibexa Connect scenario block](https://doc.ibexa.co/en/4.6/content_management/pages/ibexa_connect_scenario_block/) - Updated [Create custom Page blocks](https://doc.ibexa.co/en/4.6/content_management/pages/create_custom_page_block/#add-block-javascript) ### Customer Portal - Updated [Creating a Customer Portal](https://doc.ibexa.co/en/4.6/customer_management/cp_page_builder/) ### Personalization - [Multiple attributes in submodel computation](https://doc.ibexa.co/en/4.6/personalization/api_reference/recommendation_api/#submodel-parameters) - [Multiple attributes in submodel computation](https://doc.ibexa.co/projects/userguide/en/latest/personalization/recommendation_models/#submodels) in User Documentation ### PIM - Updated [Enable purchasing products](https://doc.ibexa.co/en/4.6/pim/enable_purchasing_products/#region-and-currency) - [Virtual products](https://doc.ibexa.co/en/4.6/pim/products/#product-types) - [Virtual products in User Documentation](https://doc.ibexa.co/projects/userguide/en/latest/pim/create_virtual_product/) - [Work with product attributes](https://doc.ibexa.co/projects/userguide/en/latest/pim/work_with_product_attributes/) in User Documentation ### REST API - Added example of input payload in JSON format for [ContentTypeCreate in REST API reference](https://doc.ibexa.co/en/4.6/api/rest_api/rest_api_reference/rest_api_reference.html#managing-content-create-content-type) - [Expected user](https://doc.ibexa.co/en/4.6/api/rest_api/rest_api_usage/rest_requests/#expected-user) header support ### Commerce - [Virtual products in checkout](https://doc.ibexa.co/en/4.6/commerce/checkout/checkout/#virtual-products-checkout) - New Order and Shipment Search Criteria: - [Order Owner Criterion](https://doc.ibexa.co/en/4.6/search/criteria_reference/order_owner_criterion/) - [Shipment Owner Criterion](https://doc.ibexa.co/en/4.6/search/criteria_reference/shipment_owner_criterion/) ### Search - REST API examples in multiple [existing Search Criteria descriptions](https://doc.ibexa.co/en/4.6/search/search_criteria_and_sort_clauses/) - New REST API-only Search Criteria: - Content search: - [ParentLocationRemoteId Criterion](https://doc.ibexa.co/en/4.6/search/criteria_reference/parentlocationremoteId_criterion/) - Product search: - [AttributeGroupIdentifier Criterion](https://doc.ibexa.co/en/4.6/search/criteria_reference/attributegroupidentifier_criterion/) - [AttributeName Criterion](https://doc.ibexa.co/en/4.6/search/criteria_reference/attributename_criterion/) - [CatalogIdentifier Criterion](https://doc.ibexa.co/en/4.6/search/criteria_reference/catalogidentifier_criterion/) - [CatalogName Criterion](https://doc.ibexa.co/en/4.6/search/criteria_reference/catalogname_criterion/) - [CatalogStatus Criterion](https://doc.ibexa.co/en/4.6/search/criteria_reference/catalogstatus_criterion/) - [FloatAttributeRange Criterion](https://doc.ibexa.co/en/4.6/search/criteria_reference/floatattributerange_criterion/) - [IntegerAttributeRange Criterion](https://doc.ibexa.co/en/4.6/search/criteria_reference/integerattributerange_criterion/) ### Infrastructure and maintenance - [Configure and customize Fastly](https://doc.ibexa.co/en/4.6/infrastructure_and_maintenance/cache/http_cache/fastly/) - Updated Security checklist: - [Block upload of unwanted file types](https://doc.ibexa.co/en/4.6/infrastructure_and_maintenance/security/security_checklist/#block-upload-of-unwanted-file-types) - [Minimise exposure](https://doc.ibexa.co/en/4.6/infrastructure_and_maintenance/security/security_checklist/#minimize-exposure) ## June 2023 ### Personalization - [Email triggers](https://doc.ibexa.co/en/4.6/personalization/integrate_recommendation_service/#send-messages-with-recommendations) - [Email triggers](https://doc.ibexa.co/projects/userguide/en/latest/personalization/triggers/) in User Documentation ### Search - [Updated search engines documentation](https://doc.ibexa.co/en/4.6/search/search_engines/search_engines/): - [Elasticsearch search engine](https://doc.ibexa.co/en/4.6/search/search_engines/elasticsearch/elasticsearch_overview/) - [Solr search engine](https://doc.ibexa.co/en/4.6/search/search_engines/solr_search_engine/solr_overview/) - [Legacy search engine](https://doc.ibexa.co/en/4.6/search/search_engines/legacy_search_engine/legacy_search_overview/#legacy-search-engine) ### Commerce - [Shipping methods management](https://doc.ibexa.co/projects/userguide/en/latest/commerce/shipping_management/work_with_shipping_methods/) in User Documentation - [Payment methods management](https://doc.ibexa.co/projects/userguide/en/latest/commerce/payment/work_with_payments/) in User Documentation - Stock Search Criteria and Aggregation: - [ProductStockRangeAggregation](https://doc.ibexa.co/en/4.6/search/aggregation_reference/productstockrange_aggregation/) - [ProductStock](https://doc.ibexa.co/en/4.6/search/criteria_reference/productstock_criterion/) - [ProductStockRange](https://doc.ibexa.co/en/4.6/search/criteria_reference/productstockrange_criterion/) ## May 2023 ### v4.5 - [v4.5 release notes](https://doc.ibexa.co/en/4.6/release_notes/ibexa_dxp_v4.5/) and guide on how to [update to v4.5](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.4/update_from_4.4/) ### Customer Portal - [Corporate account company and member REST API reference](https://doc.ibexa.co/en/4.6/api/rest_api/rest_api_reference/rest_api_reference.html#corporate-account) - [Creating a Customer Portal](https://doc.ibexa.co/en/4.6/customer_management/cp_page_builder/) ### Commerce - [Extending payments](https://doc.ibexa.co/en/4.6/commerce/payment/extend_payment/) - Reference for commerce-related events: - [Cart events](https://doc.ibexa.co/en/4.6/api/event_reference/cart_events/) - [Order management events](https://doc.ibexa.co/en/4.6/api/event_reference/order_management_events/) - [Payment events](https://doc.ibexa.co/en/4.6/api/event_reference/payment_events/) ## April 2023 ### Payment - [Payment management](https://doc.ibexa.co/en/4.6/commerce/payment/payment/), including [configuring payment workflow](https://doc.ibexa.co/en/4.6/commerce/payment/configure_payment/), [payment](https://doc.ibexa.co/en/4.6/commerce/payment/payment_api/), and [payment method PHP API](https://doc.ibexa.co/en/4.6/commerce/payment/payment_method_api/) ### Orders - [Order management](https://doc.ibexa.co/en/4.6/commerce/order_management/order_management/), including [configuring order workflow](https://doc.ibexa.co/en/4.6/commerce/order_management/configure_order_management/) and [Orders REST API reference](https://doc.ibexa.co/en/4.6/api/rest_api/rest_api_reference/rest_api_reference.html#orders) ### Shipping - [Shipping management](https://doc.ibexa.co/en/4.6/commerce/shipping_management/shipping_management/), including [configuring shipment workflow](https://doc.ibexa.co/en/4.6/commerce/shipping_management/configure_shipment/), [shipment](https://doc.ibexa.co/en/4.6/commerce/shipping_management/shipment_api/), and [shipping method PHP API](https://doc.ibexa.co/en/4.6/commerce/shipping_management/shipping_method_api/) ### Search - Search Criteria and Sort Clauses covering the new commerce features: - Order [Search Criteria](https://doc.ibexa.co/en/4.6/search/criteria_reference/order_search_criteria/) and [Sort Clauses](https://doc.ibexa.co/en/4.6/search/sort_clause_reference/order_sort_clauses/) - Payment [Search Criteria](https://doc.ibexa.co/en/4.6/search/criteria_reference/payment_search_criteria/) and [Sort Clauses](https://doc.ibexa.co/en/4.6/search/sort_clause_reference/payment_sort_clauses/) - Payment method [Search Criteria](https://doc.ibexa.co/en/4.6/search/criteria_reference/payment_method_search_criteria/) and [Sort Clauses](https://doc.ibexa.co/en/4.6/search/sort_clause_reference/payment_method_sort_clauses/) - Shipment [Search Criteria](https://doc.ibexa.co/en/4.6/search/criteria_reference/shipment_search_criteria/) and [Sort Clauses](https://doc.ibexa.co/en/4.6/search/sort_clause_reference/shipment_sort_clauses/) ### New Page blocks - [React app Page block](https://doc.ibexa.co/en/4.6/content_management/pages/react_app_block/) - [Bestsellers block](https://doc.ibexa.co/projects/userguide/en/latest/content_management/block_reference/#bestsellers-block) ### Others - [Translation comparison](https://doc.ibexa.co/projects/userguide/en/latest/content_management/translate_content/#translation-comparison) - [Managing Segments](https://doc.ibexa.co/projects/userguide/en/latest/personalization/segment_management/) ## March 2023 - [Order management API](https://doc.ibexa.co/en/4.6/commerce/order_management/order_management_api/) - [Customizing checkout](https://doc.ibexa.co/en/4.6/commerce/checkout/customize_checkout/) - Extended [table reusable component documentation](https://doc.ibexa.co/en/4.6/administration/back_office/back_office_elements/reusable_components/#tables) - How to [add GraphQL support to custom field types](https://doc.ibexa.co/en/4.6/api/graphql/graphql_custom_ft/) - How to [customize field type metadata](https://doc.ibexa.co/en/4.6/content_management/field_types/customize_field_type_metadata/) ## February 2023 ### Storefront - [Storefront](https://doc.ibexa.co/en/4.6/commerce/storefront/storefront/) documentation, including how to [configure](https://doc.ibexa.co/en/4.6/commerce/storefront/configure_storefront/) and [extend Storefront](https://doc.ibexa.co/en/4.6/commerce/storefront/extend_storefront/). ### Cart - [Cart](https://doc.ibexa.co/en/4.6/commerce/cart/cart/) documentation, including [PHP API](https://doc.ibexa.co/en/4.6/commerce/cart/cart_api/). ### Checkout - [Checkout](https://doc.ibexa.co/en/4.6/commerce/checkout/checkout/) documentation, including [how to configure checkout](https://doc.ibexa.co/en/4.6/commerce/checkout/configure_checkout/), description of main [PHP API methods](https://doc.ibexa.co/en/4.6/commerce/checkout/checkout_api/), and [checkout-related Twig functions](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/checkout_twig_functions/) ### Other - How to [create a Form Builder Form attribute](https://doc.ibexa.co/en/4.6/content_management/forms/create_form_attribute/) - [Update guide for v4.4](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.3/update_from_4.3/) ## January 2023 ### Page Builder - Description of new Page Builder blocks: [Catalog](https://doc.ibexa.co/projects/userguide/en/latest/content_management/block_reference/#catalog-block) and [Product collection](https://doc.ibexa.co/projects/userguide/en/latest/content_management/block_reference/#product-collection-block) ### Other - [Fastly Image Optimizer](https://doc.ibexa.co/en/4.6/content_management/images/fastly_io/) - [Storing field type settings externally](https://doc.ibexa.co/en/4.6/content_management/field_types/field_type_storage/#storing-field-type-settings-externally) # Report and follow issues If you have an Ibexa DXP subscription, report your issues through the [Service portal](https://support.ibexa.co). To do it, log in to your Service portal at , click "New Ticket", and report the issue as you would report a normal support request. Ibexa Product Support will respond, take care of the report, and keep you informed of the developments. This ensures the issue can be quickly prioritized according to its impact. ## Reporting security issues > **Caution: Security issues** > > If you discover a security issue, please don't report it through regular channels, but instead take a look at the [Security section](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/security/reporting_issues/index.md). # Contribute translations If you'd like to see Ibexa DXP in your language, you can contribute to the translations. [`ibexa-i18n`](https://github.com/ibexa/i18n) contains the XLIFF files providing translations under a `/translations` directory. You can use an XLIFF editor of your choice to contribute strings in your language. > **Note: Professional localization** > > Translations into French, German and Spanish are managed through Ibexa’s internal localization process and are therefore not open for community contributions. ## Translating interface using Crowdin If you wish to contribute to an existing translation of back office or start a new one you can use the Crowdin website. Visit [Ibexa DXP's Crowdin page](https://crowdin.com/project/ibexa-dxp), choose a language, and you can see a list of files containing strings. Here you can suggest your translations. If the language you want to translate to isn't available, you can ask for it to be added in the [Crowdin discussion forum for Ibexa DXP](https://crowdin.com/project/ibexa-dxp/discussions). Crowdin then automatically creates a GitHub PR and the translations are updated in the product. # Package and bundle structure and namespaces If you wish to contribute to Ibexa DXP development, you need to adhere to the package and bundle structure and namespace standards. The following conventions apply to contributions to Ibexa core code, not to third party packages. > **Note: Note** > > New code needs to follow the rules outlined here. They're being applied progressively to existing code. ## Root PHP namespace Define Ibexa DXP core PHP code in a namespace with the following prefix: ``` namespace Ibexa; ``` A package which groups some DXP features can use an additional prefix, for example: ``` namespace Ibexa\Commerce; ``` ``` namespace Ibexa\Personalization; ``` ## Packages The general package directory structure and corresponding PHP namespace mapping are: ``` . +-- src | +-- bundle (`Ibexa\Bundle\`) | +-- contracts (`Ibexa\Contracts\`) | +-- lib (`Ibexa\`) +-- tests | +-- bundle (`Ibexa\Tests\Bundle\`) | +-- integration (`Ibexa\Tests\Integration\`) | +-- lib (`Ibexa\Tests\`) ``` If a package doesn't contain some of the described parts, you can skip those directories. ### Implementation (lib) The `src/lib` directory and its corresponding `Ibexa\` namespace are meant for internal implementation not tied to the Symfony Framework. Examples: ``` namespace Ibexa\Search; ``` ``` namespace Ibexa\Commerce\Shop; ``` ### Bundles The bundle class definition in the `src/bundle` directory must be: ``` namespace Ibexa\Bundle\; class Ibexa[ProductGroup]Bundle // ... ``` Examples: ``` namespace Ibexa\Bundle\Search; class IbexaSearchBundle // ... ``` ``` namespace Ibexa\Bundle\Commerce\Shop; class IbexaCommerceShopBundle // ... ``` ### Contracts A package may introduce a namespace for contracts, to be consumed by first and third party packages and projects, which must be prefixed as: ``` namespace Ibexa\Contracts; ``` Examples: ``` namespace Ibexa\Contracts\Kernel; ``` ``` namespace Ibexa\Contracts\SiteFactory; ``` ``` namespace Ibexa\Contracts\Commerce\Shop; ``` That namespace needs to be mapped to the `src/contracts` directory of a package. > **Note: Note** > > Backward compatibility for interfaces and objects defined in the `Contracts` namespace is guaranteed. # Product guides # Product guides Ibexa DXP product editions come with a variety of features. Discover the primary ones with the help of product guides. Condensed content allows you to quickly learn about their availability, capabilities, and benefits. - [User management product guide](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/users/user_management_guide/): Find out what's user management and check what functions Ibexa DXP offers in this area to effectively manage the digital ecosystem. - [Content management product guide](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/content_management/content_management_guide/): Read the content management product guide and learn how to create, modify, and display information to the target audience. - [Discounts product guide](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/discounts/discounts_guide/): Discount enable reducing prices on products or product categories based on a detailed logic resolution. - [Online Editor product guide](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/content_management/rich_text/online_editor_guide/): Learn how to use the Online Editor, a tool that allows you to edit RichText Fields in any content item in Ibexa DXP. - [Page Builder product guide](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/content_management/pages/page_builder_guide/): Read about the Page Builder - a powerful tool for creating and modifying pages in Ibexa DXP. - [Form Builder product guide](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/content_management/forms/form_builder_guide/): See the Form Builder product guide and learn how to create various forms to increase the functionality of your website. - [Collaborative editing product guide](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/content_management/collaborative_editing/collaborative_editing_guide/): The Collaborative editing product guide provides a full description of the features and benefits that this module brings to the clients. - [Customer Portal](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/customer_management/customer_portal/): Customer Portal allows your business clients to create and manage their company accounts. - [Personalization product guide](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/personalization/personalization_guide/): Discover Personalization - a cloud-based service that tracks and analyzes customer behaviors. - [Product catalog guide](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/product_catalog/product_catalog_guide/): The product catalog guide provides a full description of the features and capabilities for managing products, their specifications, variants, pricing, and organization. - [Quable product guide](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/product_catalog/quable/quable_guide/): The Quable product guide describes how you can use the product data from Quable in Ibexa DXP to create marketing campaigns built around your products. - [Shopping list feature guide](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/commerce/shopping_list/shopping_list_guide/): A shopping list allows users to save potential purchases, recurring product sets, and other items for future use in the cart. - [Ibexa Cloud product guide](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/ibexa_cloud/ibexa_cloud_guide/): Learn how to host your application and improve your business processes by using Ibexa Cloud hosting platform. - [Ibexa Customer Data Platform (CDP) product guide](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/cdp/cdp_guide/): The Ibexa CDP product guide describes all the possibilities that the Customer Data Platform offers to help you build great customer experiences. - [Raptor integration guide](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/recommendations/raptor_integration/raptor_connector_guide/): Discover Raptor integration - an add-on focused on recommendations and tracking customer behaviors. # Release notes # Release notes The latest stable and LTS (Long Term Support) version of Ibexa DXP is [Ibexa DXP v5.0](https://doc.ibexa.co/en/latest/release_notes/ibexa_dxp_v5.0/index.md). - [Ibexa DXP v5.0 LTS](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/release_notes/ibexa_dxp_v5.0/): Ibexa DXP v5.0 incorporates features brought by LTS Updates from previous versions, brings upgrades to the tech stack and improvements to developer experience. - [Ibexa DXP v4.6 LTS](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/release_notes/ibexa_dxp_v4.6/): Ibexa DXP v4.6 brings improvements to Commerce, product catalog and Personalization offerings, and a number of changes in CDP and Ibexa Connect. - [Ibexa DXP v3.3](https://ez-systems-developer-documentation--3161.com.readthedocs.build/en/3161/release_notes/ibexa_dxp_v3.3/): Ibexa DXP v3.3 is a Long Term Support release that offers a new Personalization UI, Image Editor and a data migration bundle. # Ibexa DXP v5.0 LTS ## Google Gemini connector v5.0.7 (Headless, Experience, Commerce, LTS Update, New feature, First release) 2026-04-20 This release introduces a new AI connector that allows you to integrate [AI Actions](https://doc.ibexa.co/en/5.0/ai_actions/ai_actions/) with [Google Gemini](https://gemini.google/overview/#what-gemini-is). You can also use it as an alternative embeddings provider for the [taxonomy suggestions feature](https://doc.ibexa.co/en/latest/content_management/taxonomy/taxonomy/#taxonomy-suggestions). For more information, see how to [install and configure the Google Gemini connector](https://doc.ibexa.co/en/5.0/ai_actions/configure_ai_actions/#install-google-gemini-connector). ## Integrated help v5.0.7 (Headless, Experience, Commerce, LTS Update, New feature) 2026-04-20 ### Product tour The product tour is a new Integrated help feature that helps back office contributors to discover Ibexa DXP. With product tours, you can create customized onboarding journeys. This accelerates user adoption, reduces training time, and helps users confidently navigate the platform. For more information, see [Product tour](https://doc.ibexa.co/en/5.0/administration/back_office/product_tour/). ## Ibexa DXP v5.0.7 (Headless, Experience, Commerce, New feature) 2026-04-20 ### Security This release includes security fixes. To learn more, see the [corresponding security advisory](https://developers.ibexa.co/security-advisories/ibexa-sa-2026-002-access_control-in-security.yaml-not-working). ### Raptor connector The Raptor connector provides a seamless integration between Ibexa DXP and [Raptor Recommendation Engine](https://www.raptorservices.com/website-recommendations/). For more information, see [Raptor connector](https://doc.ibexa.co/en/5.0/recommendations/raptor_integration/raptor_connector/). #### Tracking This add-on includes two Twig functions to ease tracking setting: - `ibexa_tracking_script` to load the JavaScript tracking code, for more information, see [Tracking script](https://doc.ibexa.co/en/5.0/recommendations/raptor_integration/tracking_functions/) - `ibexa_tracking_track_event` to send tracking events from your pages, for more information, see [Tracking event function](https://doc.ibexa.co/en/5.0/recommendations/raptor_integration/tracking_functions/) #### Recommendations blocks (Experience) (Commerce) This add-on introduces a set of recommendation blocks available in the [Page Builder](https://doc.ibexa.co/en/5.0/content_management/pages/page_builder_guide/), designed to suggest relevant content or products to users, such as the most popular items or viewed by others. For more information about Recommendation blocks in Page Builder, see the relevant [Developer Documentation](https://doc.ibexa.co/en/5.0/recommendations/raptor_integration/recommendation_blocks/) and [User Documentation](https://doc.ibexa.co/projects/userguide/en/5.0/recommendations/raptor_integration/raptor_recommendation_blocks/). ### Quable PIM The Quable integration add-on allows you to connect Ibexa DXP with [Quable Product Information Management (PIM)](https://www.quable.com/en), making Quable the authoritative source of product information for every website powered by Ibexa DXP. Quable can serve as the single source of truth for all product data, including attributes, classifications, variants, and translations. Ibexa DXP consumes this data and makes it available for use in content and digital experiences. For more information, see [Quable PIM Integration](https://doc.ibexa.co/en/5.0/product_catalog/quable/quable/). ### AI Actions in Page Builder blocks (Experience) (Commerce) You can now use the [refining text AI Actions](https://doc.ibexa.co/en/5.0/ai_actions/ai_actions_guide/#refining-text) in Page Builder blocks string and text inputs. ### Developer experience #### Symfony 7.4 Symfony is upgraded from 7.3 to 7.4. It's the latest [LTS release](https://symfony.com/releases#long-term-support-release), maintained till November 2029. See [what's new in Symfony 7.4](https://symfony.com/blog/category/living-on-the-edge/8.0-7.4) and [how to update Symfony within Ibexa DXP](https://doc.ibexa.co/en/5.0/update_and_migration/from_5.0/update_from_5.0/#update-symfony-from-73-to-74). #### Taxonomy search One [taxonomy search](https://doc.ibexa.co/en/5.0/content_management/taxonomy/taxonomy_api/#search) criterion is added: - [`TaxonomyNoEntries`](https://doc.ibexa.co/en/5.0/search/criteria_reference/taxonomy_no_entries/) to find content items to which no taxonomy entries have been assigned. #### Custom parameters in `ibexa_render()` You can now pass custom parameters to templates when using the `ibexa_render()` Twig function with the new `params` option, similar to how you can with `render(controller())`. This allows you to provide additional context or data to your view templates: ``` {{ ibexa_render(content, { 'viewType': 'line', 'method': 'inline', 'params': { 'custom_param': 'custom_value', 'another_param': 'another_value' } }) }} ``` The parameters are available in your template as regular variables. For more information, see [`ibexa_render()` Twig function](https://doc.ibexa.co/en/5.0/templating/twig_function_reference/content_twig_functions/#ibexa_render). #### Try-catch support in data migrations Data migrations now support try-catch error handling, allowing you to wrap migration steps with exception handling logic. You can use it for migrations that might fail under certain conditions but should not break the entire migration process. For example, you can create languages without checking if they already exist: ``` - type: try_catch mode: execute allowed_exceptions: - Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException stop_after_first_exception: true steps: - type: language mode: create metadata: languageCode: ger-DE name: German enabled: true ``` The `try_catch` step allows you to specify which exceptions to catch and whether to continue executing remaining steps after an exception occurs. For more information, see [Error handling with try-catch](https://doc.ibexa.co/en/5.0/content_management/data_migration/importing_data/#error-handling-with-try-catch). #### Translation-related Twig Component groups Four new [Twig component groups](https://doc.ibexa.co/en/5.0/templating/components/) related to Admin UI translation are added: - `admin-ui-product-translation-modal-footer` - `admin-ui-product-translations-actions-modal` - `admin-ui-product-translations-actions` - `admin-ui-product-translations-row-actions` For more information, see [available Admin UI Twig Component groups](https://doc.ibexa.co/en/5.0/administration/back_office/back_office_elements/custom_components/#admin-ui). #### REST API You can now find examples for some REST request bodies in the [OpenAPI REST API](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_usage/rest_api_usage/#openapi-support): - in the right column of the [online reference](https://doc.ibexa.co/en/5.0/api/rest_api/rest_api_reference/rest_api_reference.html), and in the downloadable OpenAPI specification files - on your dev instance at `/api/ibexa/v2/doc` in an “Example Value” tab of the "Request Body" section, alongside the "Schema" tab - in the generated JSON or YAML OpenAPI specifications when running `ibexa:openapi` command #### PHP API The following additions were made to the PHP API: - [`Ibexa\Contracts\Core\FieldType\ReferenceAwareExternalStorage`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-FieldType-ReferenceAwareExternalStorage.html) - [`Ibexa\Contracts\Core\Options\Context`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Options-Context.html) - [`Ibexa\Contracts\CorporateAccount\Order\OrderStatusLabelProviderInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-CorporateAccount-Order-OrderStatusLabelProviderInterface.html) - [`Ibexa\Contracts\ProductCatalog\Events\ProductAttributeRenderEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Events-ProductAttributeRenderEvent.html) - [`Ibexa\Contracts\Taxonomy\Search\Query\Criterion\TaxonomyNoEntries`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Taxonomy-Search-Query-Criterion-TaxonomyNoEntries.html) For more information, see [search criteria reference entry](https://doc.ibexa.co/en/5.0/search/criteria_reference/taxonomy_no_entries/). - [`Ibexa\Contracts\ConnectorRaptor` namespace](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/namespaces/ibexa-contracts-connectorraptor.html) from the [Raptor connector add-on](https://doc.ibexa.co/en/5.0/cdp/raptor_integration/raptor_connector/) - [`Ibexa\Contracts\IntegratedHelp` namespace](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/namespaces/ibexa-contracts-integratedhelp.html) from the [Integrated help LTS-Update](https://doc.ibexa.co/en/5.0/administration/back_office/integrated_help/) ### Full changelog To learn more about all the included changes, see the full release change logs: - [Ibexa Headless v5.0.7](https://github.com/ibexa/headless/releases/tag/v5.0.7) - [Ibexa Experience v5.0.7](https://github.com/ibexa/experience/releases/tag/v5.0.7) - [Ibexa Commerce v5.0.7](https://github.com/ibexa/commerce/releases/tag/v5.0.7) To update your application, see the [update instructions](https://doc.ibexa.co/en/5.0/update_and_migration/from_5.0/update_from_5.0/#v507). ## Shopping Lists v5.0.6 (Commerce, LTS Update, New feature) 2026-03-05 Shopping list is a new feature that allows users to save products into wishlists. An authenticated customer has a default "My wishlist", and can create custom shopping lists to organize their potential or recurrent purchases. Products can be moved from cart to shopping list, from a shopping list to another shopping list, and copied from a shopping list to the cart. For more information, see [Shopping list feature guide](https://doc.ibexa.co/en/5.0/commerce/shopping_list/shopping_list_guide/). ## Ibexa DXP v5.0.6 (Headless, Experience, Commerce, New feature) 2026-03-05 ### Security This release includes security fixes. To learn more, see the [corresponding security advisory](https://developers.ibexa.co/security-advisories/ibexa-sa-2026-001-insufficient-main-landing-page-access-control). ### Improved product variant querying Product variant querying now supports filtering by variant codes and product attribute criteria. You can now use the [`ProductServiceInterface::findVariants()`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-ProductServiceInterface.html#method_findVariants) method to search for variants across all products, regardless of their base product. For more information, see [Product API - Searching variants](https://doc.ibexa.co/en/5.0/pim/product_api/#searching-for-variants-across-all-products). ### Infrastructure #### Ibexa Cloud package A new `ibexa/cloud` package is now available for Ibexa Cloud deployments. This package replaces the previous `composer ibexa:setup --platformsh` command with a dedicated console command. The package automatically generates environment variables based on the configuration of relationships and routes in Ibexa Cloud, making it easier to configure services like databases, cache, search engines, and session storage. For more information, see [Install on Ibexa Cloud](https://doc.ibexa.co/en/5.0/ibexa_cloud/install_on_ibexa_cloud/) and [Environment variables on Ibexa Cloud](https://doc.ibexa.co/en/5.0/ibexa_cloud/environment_variables/). #### PHP 8.4 support PHP 8.4 is now [officially supported](https://doc.ibexa.co/en/5.0/getting_started/requirements/#php). ### Query subtree limit configuration A new `query_subtree.limit` configuration option improves performance when working with large content trees by limiting count operations. This prevents performance degradation from database queries when determining if locations have children or calculating subtree sizes. For more information, see [Subtree operations configuration](https://doc.ibexa.co/en/5.0/administration/back_office/back_office_configuration/#subtree-operations). ### Improved HTTP caching for Page Builder and dashboard blocks (Experience) (Commerce) You can now indicate which [query parameters](https://en.wikipedia.org/wiki/Query_string) must be used as keys when generating [HTTP cache](https://doc.ibexa.co/en/5.0/infrastructure_and_maintenance/cache/http_cache/http_cache/) for block requests. This allows you to improve performance for blocks by utilizing HTTP cache more effectively, for example, for paginated blocks in the [dashboard](https://doc.ibexa.co/en/5.0/administration/dashboard/customize_dashboard/). To set it up, use the new `cacheable_query_params` [block setting](https://doc.ibexa.co/en/5.0/content_management/pages/page_blocks/#block-configuration). Then, adjust your [layouts](https://doc.ibexa.co/en/5.0/templating/render_content/render_page/#configure-layout) and pass the parameters to [Symfony's `controller function`](https://symfony.com/doc/7.4/reference/twig_reference.html#controller) by using the new `ibexa_append_cacheable_query_params` Twig function, as in the example below: ``` {{ render_esi(controller('Ibexa\\Bundle\\FieldTypePage\\Controller\\BlockController::renderAction', { 'locationId': locationId, 'contentId': contentInfo.id, 'blockId': block.id, 'versionNo': versionInfo.versionNo, 'languageCode': field.languageCode }, ibexa_append_cacheable_query_params(block) )) }} ``` ### Developer experience #### PHP API The following additions were made to the PHP API: - [`Ibexa\Contracts\Cdp\Value\Webhook\PersonIdType`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Cdp-Value-Webhook-PersonIdType.html) - [`Ibexa\Contracts\Cdp\Webhook`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/namespaces/ibexa-contracts-cdp-webhook.html) - [`Ibexa\Contracts\Core\Persistence\Filter\Query`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/namespaces/ibexa-contracts-core-persistence-filter-query.html) - [`Ibexa\Contracts\ImageEditor\Event`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/namespaces/ibexa-contracts-imageeditor-event.html) - [`Ibexa\Contracts\ProductCatalog\Config`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/namespaces/ibexa-contracts-productcatalog-config.html) - [`Ibexa\Contracts\ShoppingList`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/namespaces/ibexa-contracts-shoppinglist.html) - [`Ibexa\Contracts\Taxonomy\Embedding\Exception`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/namespaces/ibexa-contracts-taxonomy-embedding-exception.html) ### Full changelog To learn more about all the included changes, see the full release change logs: - [Ibexa Headless v5.0.6](https://github.com/ibexa/headless/releases/tag/v5.0.6) - [Ibexa Experience v5.0.6](https://github.com/ibexa/experience/releases/tag/v5.0.6) - [Ibexa Commerce v5.0.6](https://github.com/ibexa/commerce/releases/tag/v5.0.6) To update your application, see the [update instructions](https://doc.ibexa.co/en/5.0/update_and_migration/from_5.0/update_from_5.0/#v506). ## Ibexa DXP v5.0.5 (Headless, Experience, Commerce) 2026-01-15 ### Infrastructure #### Added support for Elasticsearch 8 Elasticsearch 8 is now officially supported. If you're currently using Elasticsearch 7, which is [no longer maintained](https://www.elastic.co/support/eol), it's recommended to upgrade. See the [update instructions](https://doc.ibexa.co/en/5.0/update_and_migration/from_5.0/update_from_5.0/#update-elasticsearch-server) for more information. #### Added support for Valkey Valkey is now [officially supported](https://doc.ibexa.co/en/5.0/getting_started/requirements/) alongside Redis. #### Added support for PostgreSQL 18 PostgreSQL 18 is now [officially supported](https://doc.ibexa.co/en/5.0/getting_started/requirements#dbms). ### Developer experience #### Easier debugging of Page Builder blocks In Symfony's `dev` environment, use the "Open profiler" action to quickly debug Page Builder's block rendering failures. *[Image: Quickly debug failing Page Builder blocks with "Open profiler" action]* #### Improved logging for Ibexa CDP You can configure the new `ibexa.cdp.webhook` Monolog channels to direct all CDP webhook logs to specific output for easier separation of logs. Example configuration: ``` when@prod: monolog: handlers: cdp_webhook: type: stream path: "%kernel.logs_dir%/cdp_webhook_%kernel.environment%.log" level: debug channels: [ 'ibexa.cdp.webhook' ] ``` #### Added OpenAPI support for Collaborative editing REST API The [Collaborative editing](https://doc.ibexa.co/en/5.0/content_management/collaborative_editing/collaborative_editing/) REST API endpoints are now included in the [OpenAPI-based REST API reference](https://doc.ibexa.co/en/5.0/api/rest_api/rest_api_reference/rest_api_reference.html#tag/Collaboration-Sessions). ### Full changelog To learn more about all the included changes, see the full release change logs: - [Ibexa Headless v5.0.5](https://github.com/ibexa/headless/releases/tag/v5.0.5) - [Ibexa Experience v5.0.5](https://github.com/ibexa/experience/releases/tag/v5.0.5) - [Ibexa Commerce v5.0.5](https://github.com/ibexa/commerce/releases/tag/v5.0.5) To update your application, see the [update instructions](https://doc.ibexa.co/en/5.0/update_and_migration/from_5.0/update_from_5.0/#v505). ## Integrated help v5.0.4 (Headless, Experience, Commerce, LTS Update, New feature, First release) 2025-12-10 Integrated help brings contextual documentation, guidance, and partner-specific resources right into the user interface of Ibexa DXP. It helps editors, store managers, and developers to quickly access relevant content, training and resources without leaving the UI, narrowing the gap between product and documentation. The default help menu can be modified to include links to internal editorial guidelines, custom tutorials, or support pages. *[Image: Integrated help menu]* For more information, see [Integrated help](https://doc.ibexa.co/en/5.0/administration/back_office/integrated_help/). ## Anthropic connector v5.0.4 (Headless, Experience, Commerce, LTS Update, New feature, First release) 2025-12-10 This release introduces a new AI connector that allows you to integrate [AI Actions](https://doc.ibexa.co/en/5.0/ai_actions/ai_actions/) with [Anthropic Claude](https://claude.com/product/overview). For more information, see how to [install Anthropic connector](https://doc.ibexa.co/en/5.0/ai_actions/configure_ai_actions#install-anthropic-connector). ## Ibexa DXP v5.0.4 (Headless, Experience, Commerce, New feature) 2025-12-10 ### Security This release includes security fixes. To learn more, see the [corresponding security advisory](https://developers.ibexa.co/security-advisories/ibexa-sa-2025-005-password-change-and-xss-vulnerabilities-in-back-office). ### Real-time collaborative editing Real-time editing is now part of the [Collaborative editing](https://doc.ibexa.co/en/5.0/content_management/collaborative_editing/collaborative_editing/) feature. By using it, users can edit and review content in real time, making teamwork faster, more efficient, and streamlining the content review process. The system automatically tracks changes, allowing seamless collaboration within a single content item. This extends the already existing capabilities allowing editors to work on the same content created in Ibexa DXP simultaneously, streamlining the content creation and review process. *[Image: Participants list]* For more information, see how to [configure Collaborative editing](https://doc.ibexa.co/en/5.0/content_management/collaborative_editing/configure_collaborative_editing/). ### Taxonomy suggestions for faster content classification You can now speed up taxonomy assignment with AI-powered taxonomy suggestions. Instead of manually browsing through large taxonomy trees and selecting categories or tags one by one, editors can choose from automatically generated suggestions based on the product or content information, for example name and description. This approach reduces manual effort, minimizes errors, and significantly improves the speed and consistency of content and product classification. *[Image: Taxonomy entries suggested by the AI Assistant]* For more information, see [Taxonomy suggestions](https://doc.ibexa.co/en/5.0/content_management/taxonomy/taxonomy/#taxonomy-suggestions). ### Infrastructure - MariaDB 11.4 is now [officially supported](https://doc.ibexa.co/en/5.0/getting_started/requirements/#dbms) ### Developer experience #### PHP API The following additions were made to the PHP API: ##### Real-time collaborative editing: - [`Ibexa\Contracts\Collaboration\Invitation\Query\Criterion\ParticipantScope`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Invitation-Query-Criterion-ParticipantScope.html) - [`Ibexa\Contracts\Collaboration\Invitation\Query\Criterion\ParticipantType`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Invitation-Query-Criterion-ParticipantType.html) - [`Ibexa\Contracts\Collaboration\Participant\ParticipantDiscriminator`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Participant-ParticipantDiscriminator.html) - [`Ibexa\Contracts\FieldTypeRichTextRTE\ChannelIdGeneratorInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-FieldTypeRichTextRTE-ChannelIdGeneratorInterface.html) - [`Ibexa\Contracts\FieldTypeRichTextRTE\Config\LicenseKeyProviderInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-FieldTypeRichTextRTE-Config-LicenseKeyProviderInterface.html) - [`Ibexa\Contracts\FieldTypeRichTextRTE\Config\LocalStorageInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-FieldTypeRichTextRTE-Config-LocalStorageInterface.html) - [`Ibexa\Contracts\FieldTypeRichTextRTE\TokenServiceInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-FieldTypeRichTextRTE-TokenServiceInterface.html) - [`Ibexa\Contracts\FieldTypeRichTextRTE\ToS\LicenseTermsStatusServiceInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-FieldTypeRichTextRTE-ToS-LicenseTermsStatusServiceInterface.html) - [`Ibexa\Contracts\FieldTypeRichTextRTE\ToS\NoResponseException`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-FieldTypeRichTextRTE-ToS-NoResponseException.html) - [`Ibexa\Contracts\FieldTypeRichTextRTE\ToS\Status`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-FieldTypeRichTextRTE-ToS-Status.html) - [`Ibexa\Contracts\FieldTypeRichTextRTE\ToS\ToSServiceInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-FieldTypeRichTextRTE-ToS-ToSServiceInterface.html) - [`Ibexa\Contracts\Share\Mapper\Action\ShareActionItemsMapperInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Share-Mapper-Action-ShareActionItemsMapperInterface.html) ##### AI Taxonomy suggestions: - [`Ibexa\Contracts\ConnectorAi\Action\DataType\Taxonomy`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-DataType-Taxonomy.html) - [`Ibexa\Contracts\ConnectorAi\Action\DataType\TaxonomyEntry`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-DataType-TaxonomyEntry.html) - [`Ibexa\Contracts\ConnectorAi\Action\DataType\TaxonomySuggestion`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-DataType-TaxonomySuggestion.html) - [`Ibexa\Contracts\ConnectorAi\Action\DataType\TaxonomySuggestionInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-DataType-TaxonomySuggestionInterface.html) - [`Ibexa\Contracts\ConnectorAi\Action\DataType\TextToTaxonomyInput`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-DataType-TextToTaxonomyInput.html) - [`Ibexa\Contracts\ConnectorAi\Action\Response\TaxonomyResponse`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-Response-TaxonomyResponse.html) - [`Ibexa\Contracts\ConnectorAi\Action\SuggestTaxonomyAction`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-SuggestTaxonomyAction.html) - [`Ibexa\Contracts\ConnectorAi\Action\TextToTaxonomy\Action`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-TextToTaxonomy-Action.html) - [`Ibexa\Contracts\ConnectorAi\Action\TextToTaxonomy\ActionResponse`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-TextToTaxonomy-ActionResponse.html) - [`Ibexa\Contracts\ConnectorAi\Action\TextToTaxonomy\ActionType`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-TextToTaxonomy-ActionType.html) - [`Ibexa\Contracts\Core\Repository\Values\Content\EmbeddingQuery`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-EmbeddingQuery.html) - [`Ibexa\Contracts\Core\Repository\Values\Content\EmbeddingQueryBuilder`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-EmbeddingQueryBuilder.html) - [`Ibexa\Contracts\Core\Repository\Values\Content\Query\Embedding`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Embedding.html) - [`Ibexa\Contracts\Core\Repository\Values\Content\QueryValidatorInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-QueryValidatorInterface.html) - [`Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContentTypeGroupName`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-ContentType-Query-Criterion-ContentTypeGroupName.html) - [`Ibexa\Contracts\Core\Search\Embedding\EmbeddingConfigurationInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Search-Embedding-EmbeddingConfigurationInterface.html) - [`Ibexa\Contracts\Core\Search\Embedding\EmbeddingProviderInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Search-Embedding-EmbeddingProviderInterface.html) - [`Ibexa\Contracts\Core\Search\Embedding\EmbeddingProviderRegistryInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Search-Embedding-EmbeddingProviderRegistryInterface.html) - [`Ibexa\Contracts\Core\Search\Embedding\EmbeddingProviderResolverInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Search-Embedding-EmbeddingProviderResolverInterface.html) - [`Ibexa\Contracts\Core\Search\Embedding\EmbeddingResolverNotFoundException`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Search-Embedding-EmbeddingResolverNotFoundException.html) - [`Ibexa\Contracts\Core\Search\FieldType\EmbeddingField`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Search-FieldType-EmbeddingField.html) - [`Ibexa\Contracts\Core\Search\FieldType\EmbeddingFieldFactory`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Search-FieldType-EmbeddingFieldFactory.html) - [`Ibexa\Contracts\Elasticsearch\Query\EmbeddingVisitor`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Elasticsearch-Query-EmbeddingVisitor.html) - [`Ibexa\Contracts\Solr\Query\EmbeddingVisitor`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Solr-Query-EmbeddingVisitor.html) - [`Ibexa\Contracts\Taxonomy\Embedding\TaxonomyEmbeddingConfigurationInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Taxonomy-Embedding-TaxonomyEmbeddingConfigurationInterface.html) - [`Ibexa\Contracts\Taxonomy\Embedding\TaxonomyEmbeddingFieldProviderInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Taxonomy-Embedding-TaxonomyEmbeddingFieldProviderInterface.html) - [`Ibexa\Contracts\Taxonomy\Search\Query\Value\TaxonomyEmbedding`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Taxonomy-Search-Query-Value-TaxonomyEmbedding.html) ##### Search - [`Ibexa\Contracts\AdminUi\ContentType\ContentTypeFieldsByExpressionServiceInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-AdminUi-ContentType-ContentTypeFieldsByExpressionServiceInterface.html) - [`Ibexa\Contracts\CoreSearch\Values\Query\PaginationAwareInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-CoreSearch-Values-Query-PaginationAwareInterface.html) - [`Ibexa\Contracts\SiteFactory\Values\Query\Criterion\MatchTreeRootLocationIds`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-SiteFactory-Values-Query-Criterion-MatchTreeRootLocationIds.html) ##### Other - [`Ibexa\Contracts\ProductCatalog\CapabilitiesEnum`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-CapabilitiesEnum.html) - [`Ibexa\Contracts\ProductCatalog\CapabilitiesServiceInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-CapabilitiesServiceInterface.html) - [`Ibexa\Contracts\User\PasswordReset\NotifierInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-User-PasswordReset-NotifierInterface.html) To learn more about all the included changes, see the full release change logs: - [Ibexa Headless v5.0.4](https://github.com/ibexa/headless/releases/tag/v5.0.4) - [Ibexa Experience v5.0.4](https://github.com/ibexa/experience/releases/tag/v5.0.4) - [Ibexa Commerce v5.0.4](https://github.com/ibexa/commerce/releases/tag/v5.0.4) To update your application, see the [update instructions](https://doc.ibexa.co/en/5.0/update_and_migration/from_5.0/update_from_5.0/#v504). ## Ibexa DXP v5.0.3 (Headless, Experience, Commerce) 2024-10-17 ### Security This release includes security fixes. To learn more, see the [corresponding security advisory](https://developers.ibexa.co/security-advisories/ibexa-sa-2025-004-xss-and-enumeration-vulnerabilities-in-back-office). ### Developer experience #### PHP API The PHP API has been expanded with the following: PHP API classes and interfaces - [`Ibexa\Contracts\ContentForms\Event`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/namespaces/ibexa-contracts-contentforms-event.html) - [`Ibexa\Contracts\Core\Persistence\Content\Type\CriterionHandlerInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Persistence-Content-Type-CriterionHandlerInterface.html) - [`Ibexa\Contracts\Core\Repository\Values\ContentType\Query`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/namespaces/ibexa-contracts-core-repository-values-contenttype-query.html) - [`Ibexa\Contracts\Core\Repository\Values\ContentType\Query\ContentTypeQuery`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-ContentType-Query-ContentTypeQuery.html) - [`Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/namespaces/ibexa-contracts-core-repository-values-contenttype-query-criterion.html) - [`Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-ContentType-Query-CriterionInterface.html) - [`Ibexa\Contracts\Core\Repository\Values\ContentType\Query\SortClause`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-ContentType-Query-SortClause.html) - [`Ibexa\Contracts\Core\Repository\Values\ContentType\Query\SortClause`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/namespaces/ibexa-contracts-core-repository-values-contenttype-query-sortclause.html) - [`Ibexa\Contracts\Core\Repository\Values\ContentType\SearchResult`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-ContentType-SearchResult.html) Events - [`Ibexa\Contracts\ContentForms\Event\AutosaveEnabled`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ContentForms-Event-AutosaveEnabled.html) Search criteria - [`Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContainsFieldDefinitionId`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-ContentType-Query-Criterion-ContainsFieldDefinitionId.html) - [`Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContentTypeGroupId`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-ContentType-Query-Criterion-ContentTypeGroupId.html) - [`Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContentTypeId`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-ContentType-Query-Criterion-ContentTypeId.html) - [`Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContentTypeIdentifier`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-ContentType-Query-Criterion-ContentTypeIdentifier.html) - [`Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\IsSystem`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-ContentType-Query-Criterion-IsSystem.html) - [`Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalAnd`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-ContentType-Query-Criterion-LogicalAnd.html) - [`Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalNot`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-ContentType-Query-Criterion-LogicalNot.html) - [`Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalOperator`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-ContentType-Query-Criterion-LogicalOperator.html) - [`Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalOr`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-ContentType-Query-Criterion-LogicalOr.html) Sort clauses - [`Ibexa\Contracts\Core\Repository\Values\ContentType\Query\SortClause\Id`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-ContentType-Query-SortClause-Id.html) - [`Ibexa\Contracts\Core\Repository\Values\ContentType\Query\SortClause\Identifier`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-ContentType-Query-SortClause-Identifier.html) - [`Ibexa\Contracts\Core\Repository\Values\ContentType\Query\SortClause\Name`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-ContentType-Query-SortClause-Name.html) To learn more about all the included changes, see the full release change logs: - [Ibexa Headless v5.0.3](https://github.com/ibexa/headless/releases/tag/v5.0.3) - [Ibexa Experience v5.0.3](https://github.com/ibexa/experience/releases/tag/v5.0.3) - [Ibexa Commerce v5.0.3](https://github.com/ibexa/commerce/releases/tag/v5.0.3) To update your application, see the [update instructions](https://doc.ibexa.co/en/5.0/update_and_migration/from_5.0/update_from_5.0/#v503). ## Ibexa DXP v5.0.2 (Headless, Experience, Commerce, New feature) 2025-09-09 ### Collaboration The new [Collaborative editing feature](https://doc.ibexa.co/en/5.0/content_management/collaborative_editing/collaborative_editing_guide/) allows multiple users to preview, review, and edit the same content, improving teamwork and streamlining the review process. Internal and external users can be invited to a collaboration session, through different sharing options. With Real-time editing, more advanced part of the feature, users can see each other’s changes in the real time, or work on the content asynchronously. Additionally, shared drafts can be accessed and managed through new dashboard tabs: **My shared drafts** and **Drafts shared with me**, helping users stay organized. ### Discount indexing Discounts now allow scheduling a re-indexing of discounted product catalog prices at the most convenient time by using the Ibexa Messenger package. Ibexa Messenger is a customization of the Symfony Messenger package, created to adjust it to Ibexa DXP's needs. Once properly configured, it uses a background queue to trigger price re-indexing, ensuring efficient use of system resources without causing performance disruptions. ### Improvements to notifications An improved notifications system is now more intuitive. Developers can now create and configure their own notification types, while users can now [browse through a list of notifications](https://doc.ibexa.co/projects/userguide/en/latest/getting_started/notifications/), where they can either act on them or dismiss them. *[Image: A searchable notifications list]* ### Chat GPT 5.0 support With improved reasoning and greater accuracy in mind, the AI Connector package has been enhanced by adding ChatGPT 5.0 to its list of supported LLMs. *[Image: ChatGPT 5.0 on a list of supported LLMs]* ### Developer experience #### New packages The following packages have been introduced in Ibexa DXP v5.0.2: - ibexa/collaboration - ibexa/messenger #### New version of PHP Storm Plugin To further improve your experience with Ibexa DXP, a 1.14.0 version of [PHP Storm Plugin](https://doc.ibexa.co/en/5.0/resources/phpstorm_plugin/) has been released, which brings the following changes: - Added support for Ibexa DXP v5.0 - Added compatibility with PhpStorm 2024.3.6+ - Added file template for Twig Component class - Added code completion for Twig Component Groups in YAML config files and AsTwigComponent attribute - Added code completion for Twig Component Types in YAML config files #### REST APIs Ibexa DXP v5.0.2 adds REST API coverage for the following features: - Collaboration: - Invitation - CollaborationSession - Participant - ParticipantList - AI Actions - Action - ActionType - ActionTypeList - ActionConfiguration - ActionConfigurationList - Discounts - Discount - DiscountList #### PHP API The PHP API has been expanded with the following: PHP API classes and interfaces - [`Ibexa\Contracts\AdminUi\Exception`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/namespaces/ibexa-contracts-adminui-exception.html) - [`Ibexa\Contracts\AdminUi\Exception\UnresolvedPreviewUrlException`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-AdminUi-Exception-UnresolvedPreviewUrlException.html) - [`Ibexa\Contracts\AdminUi\PreviewUrlResolver`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/namespaces/ibexa-contracts-adminui-previewurlresolver.html) - [`Ibexa\Contracts\AdminUi\PreviewUrlResolver\VersionPreviewUrlResolverInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-AdminUi-PreviewUrlResolver-VersionPreviewUrlResolverInterface.html) - [`Ibexa\Contracts\AutomatedTranslation\Exception`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/namespaces/ibexa-contracts-automatedtranslation-exception.html) - [`Ibexa\Contracts\AutomatedTranslation\Exception\ClientNotConfiguredException`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-AutomatedTranslation-Exception-ClientNotConfiguredException.html) - [`Ibexa\Contracts\Collaboration\Configuration\ShareableUserConfigurationInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Configuration-ShareableUserConfigurationInterface.html) - [`Ibexa\Contracts\Collaboration\Security`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/namespaces/ibexa-contracts-collaboration-security.html) - [`Ibexa\Contracts\Collaboration\Security\ShareableLinkMatcherStrategyInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Security-ShareableLinkMatcherStrategyInterface.html) - [`Ibexa\Contracts\Collaboration\Session\JoinSessionRedirectResolverInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Session-JoinSessionRedirectResolverInterface.html) - [`Ibexa\Contracts\Collaboration\Session\LeaveSessionRedirectResolverInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Session-LeaveSessionRedirectResolverInterface.html) - [`Ibexa\Contracts\Core\Validation\Constraint`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/namespaces/ibexa-contracts-core-validation-constraint.html) - [`Ibexa\Contracts\Core\Validation\Constraint\UniqueIdentifier`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Validation-Constraint-UniqueIdentifier.html) - [`Ibexa\Contracts\Core\Validation\Constraint\UniqueIdentifierValidator`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Validation-Constraint-UniqueIdentifierValidator.html) - [`Ibexa\Contracts\Messenger`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/namespaces/ibexa-contracts-messenger.html) - [`Ibexa\Contracts\Messenger\Transport`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/namespaces/ibexa-contracts-messenger-transport.html) - [`Ibexa\Contracts\Messenger\Transport\MessageProviderInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Messenger-Transport-MessageProviderInterface.html) - [`Ibexa\Contracts\ProductCatalog\Values\Product\Query\AttributeCriterionBuilder`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/namespaces/ibexa-contracts-productcatalog-values-product-query-attributecriterionbuilder.html) - [`Ibexa\Contracts\ProductCatalog\Values\Product\Query\AttributeCriterionBuilderRegistry`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-AttributeCriterionBuilderRegistry.html) - [`Ibexa\Contracts\ProductCatalog\Values\Product\Query\AttributeCriterionBuilderRegistryInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-AttributeCriterionBuilderRegistryInterface.html) - [`Ibexa\Contracts\ProductCatalog\Values\Product\Query\AttributeCriterionBuilder\AttributeCriterionBuilderInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-AttributeCriterionBuilder-AttributeCriterionBuilderInterface.html) - [`Ibexa\Contracts\ProductCatalog\Values\Product\Query\AttributeCriterionBuilder\CheckboxBuilder`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-AttributeCriterionBuilder-CheckboxBuilder.html) - [`Ibexa\Contracts\ProductCatalog\Values\Product\Query\AttributeCriterionBuilder\ColorBuilder`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-AttributeCriterionBuilder-ColorBuilder.html) - [`Ibexa\Contracts\ProductCatalog\Values\Product\Query\AttributeCriterionBuilder\FloatBuilder`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-AttributeCriterionBuilder-FloatBuilder.html) - [`Ibexa\Contracts\ProductCatalog\Values\Product\Query\AttributeCriterionBuilder\IntegerBuilder`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-AttributeCriterionBuilder-IntegerBuilder.html) - [`Ibexa\Contracts\ProductCatalog\Values\Product\Query\AttributeCriterionBuilder\SelectionBuilder`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-AttributeCriterionBuilder-SelectionBuilder.html) - [`Ibexa\Contracts\Share\Permission\Mapper`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/namespaces/ibexa-contracts-share-permission-mapper.html) Events - [`Ibexa\Contracts\AdminUi\Event\ResolveVersionPreviewUrlEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-AdminUi-Event-ResolveVersionPreviewUrlEvent.html) - [`Ibexa\Contracts\Collaboration\Session\Event\JoinSessionEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Session-Event-JoinSessionEvent.html) - [`Ibexa\Contracts\Collaboration\Session\Event\SessionPublicPreviewEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Session-Event-SessionPublicPreviewEvent.html) - [`Ibexa\Contracts\Discounts\Event\EnableDiscountEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-EnableDiscountEvent.html) - [`Ibexa\Contracts\Discounts\Event\BeforeDisableDiscountEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-BeforeDisableDiscountEvent.html) - [`Ibexa\Contracts\Discounts\Event\BeforeEnableDiscountEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-BeforeEnableDiscountEvent.html) - [`Ibexa\Contracts\Discounts\Event\DisableDiscountEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-DisableDiscountEvent.html) - [`Ibexa\Contracts\Share\Event\UsersWithPermissionInfoMappedEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Share-Event-UsersWithPermissionInfoMappedEvent.html) Search criteria - [`Ibexa\Contracts\Collaboration\Session\Query\Criterion\ParticipantToken`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Session-Query-Criterion-ParticipantToken.html) - [`Ibexa\Contracts\Discounts\Value\Query\Criterion\IndexedAtCriterion`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-IndexedAtCriterion.html) - [`Ibexa\Contracts\Discounts\Value\Query\Criterion\UpdatedAtCriterion`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-UpdatedAtCriterion.html) Sort clauses - [`Ibexa\Contracts\Collaboration\Invitation\Query\SortClause\CreatedAt`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Invitation-Query-SortClause-CreatedAt.html) - [`Ibexa\Contracts\Collaboration\Invitation\Query\SortClause\Id`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Invitation-Query-SortClause-Id.html) - [`Ibexa\Contracts\Collaboration\Invitation\Query\SortClause\Status`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Invitation-Query-SortClause-Status.html) - [`Ibexa\Contracts\Collaboration\Invitation\Query\SortClause\UpdatedAt`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Invitation-Query-SortClause-UpdatedAt.html) - [`Ibexa\Contracts\Collaboration\Session\Query\SortClause\CreatedAt`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Session-Query-SortClause-CreatedAt.html) - [`Ibexa\Contracts\Collaboration\Session\Query\SortClause\Id`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Session-Query-SortClause-Id.html) - [`Ibexa\Contracts\Collaboration\Session\Query\SortClause\UpdatedAt`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Session-Query-SortClause-UpdatedAt.html) ### Full changelog To learn more about all the included changes, see the full release change logs: - [Ibexa Headless v5.0.2](https://github.com/ibexa/headless/releases/tag/v5.0.2) - [Ibexa Experience v5.0.2](https://github.com/ibexa/experience/releases/tag/v5.0.2) - [Ibexa Commerce v5.0.2](https://github.com/ibexa/commerce/releases/tag/v5.0.2) To update your application, see the [update instructions](https://doc.ibexa.co/en/5.0/update_and_migration/from_5.0/update_from_5.0/#v502). ## Ibexa DXP v5.0.1 (Headless, Experience, Commerce, New feature) 2025-08-19 ### Special characters in online editor The [online editor](https://doc.ibexa.co/en/5.0/content_management/rich_text/online_editor_guide/) now allows to easily enter special characters like currency symbols. It uses the [special characters plugin](https://ckeditor.com/docs/ckeditor5/latest/features/special-characters.html). *[Image: Special characters in online editor]* ### Support for Solr 9 With this release, Ibexa DXP starts supporting [Solr 9](https://doc.ibexa.co/en/5.0/getting_started/requirements/#search). Solr 9 comes with support for [Dense Vector Search](https://solr.apache.org/guide/solr/latest/query-guide/dense-vector-search.html), paving the way for incoming improvements to the [AI Actions](https://doc.ibexa.co/en/5.0/ai_actions/ai_actions/) feature. ### Improved content creation interface The editing interface of the back office is now improved to better highlight the language, creator, and the publication date when working with content items. *[Image: Improved interface for content creation]* ### Taxonomy Subtree limitation You can now manage access to [taxonomy items](https://doc.ibexa.co/en/5.0/content_management/taxonomy/taxonomy/) more effectively by using the new [Taxonomy Subtree limitation](https://doc.ibexa.co/en/5.0/permissions/limitation_reference/#taxonomy-subtree-limitation). In addition, you can now use the [Taxonomy limitation](https://doc.ibexa.co/en/5.0/permissions/limitation_reference/#taxonomy-limitation) together with the `taxonomy/assign` policy. ### Base price column added to a Product Picker view The Product Picker tool that, for example, lets you [select products eligible for discounts](https://doc.ibexa.co/projects/userguide/en/5.0/commerce/discounts/work_with_discounts/#create-new-discount), now displays a **Base price** column for products and product variants. ### PHP API The PHP API has been enhanced with the following new classes: [`Ibexa\Contracts\Cart\Exception\VatCalculationExceptionInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Cart-Exception-VatCalculationExceptionInterface.html) [`Ibexa\Contracts\ProductCatalog\Values\Product\Query\Criterion\AbstractPriceRange`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-Criterion-AbstractPriceRange.html) [`Ibexa\Contracts\ProductCatalog\Values\Product\Query\Criterion\CustomPriceRange`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-Criterion-CustomPriceRange.html) This release brings additional minor improvements to the developer's experience that result from capabilities offered by PHP in version 8.3. ### Full changelog To learn more about all the included changes, see the full release change logs: - [Ibexa Headless v5.0.1](https://github.com/ibexa/headless/releases/tag/v5.0.1) - [Ibexa Experience v5.0.1](https://github.com/ibexa/experience/releases/tag/v5.0.1) - [Ibexa Commerce v5.0.1](https://github.com/ibexa/commerce/releases/tag/v5.0.1) To update your application, see the [update instructions](https://doc.ibexa.co/en/5.0/update_and_migration/from_5.0/update_from_5.0/#v501). ## Ibexa DXP v5.0.0 (Headless, Experience, Commerce, New feature, First release) 2025-07-22 ### Notable changes This version incorporates into the product numerous features brought by LTS Updates from previous versions, brings upgrades to the tech stack and improvements to developer experience. #### AI Actions The AI Actions feature enhances the usability and flexibility of Ibexa DXP by harnessing the potential of artificial intelligence to automate time-consuming editorial tasks. By default, the AI Actions feature can help users with their work in following scenarios: - Refining text: when editing a content item, users can request that a passage selected in online editor is modified, for example, by adjusting the length of the text, changing its tone, or correcting linguistic errors - Generating alternative text: when working with images, users can ask AI to generate alternative text for them, which helps improve accessibility and SEO *[Image: AI Assistant]* AI Actions integrate with [Ibexa Connect](https://doc.ibexa.co/projects/connect/en/latest), giving you an opportunity to build complex data transformation workflows without having to rely on custom code. For more information, see [AI Actions product guide](https://doc.ibexa.co/en/5.0/ai_actions/ai_actions_guide/). #### Discounts (Commerce) With Discounts, you can temporarily or permanently reduce prices on specific products or categories, making deals more attractive to potential buyers. Use them to encourage first-time purchases, reward loyal customers, promote new or slow-moving items, or drive sales during seasonal events. By displaying discounted prices clearly in the catalog or cart, businesses can create a sense of urgency, increase customer satisfaction, and ultimately boost revenue. *[Image: Discounts for products in the cart]* For more information, see [Discounts product guide](https://doc.ibexa.co/en/5.0/discounts/discounts_guide/). #### Date and time attribute The Date and time attributes allow you to represent date and time values as part of the product specification in the [product catalog](https://doc.ibexa.co/en/5.0/product_catalog/product_catalog_guide). For more information, see [Date and time attributes](https://doc.ibexa.co/en/5.0/product_catalog/attributes/date_and_time/). #### Symbol attribute The Symbol attributes allow you to efficiently represent the string-based data as part of the product specification in the [product catalog](https://doc.ibexa.co/en/5.0/product_catalog/product_catalog_guide). For more information, see [Symbol attributes](https://doc.ibexa.co/en/5.0/product_catalog/attributes/symbol_attribute_type/). #### Collaboration With Collaboration, multiple users can invite each other to work on the same content. It is a starting point for future functionalities in the collaboration domain. *[Image: Collaboration invite]* For more information, see [Collaboration PHP API](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/namespaces/ibexa-contracts-collaboration.html) and [Share PHP API](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/namespaces/ibexa-contracts-share.html). ### Software architecture upgrades With improved compatibility, performance and increased security, as well as better developer experience in mind, Ibexa decided to introduce several significant tech stack upgrades. For a full list of updated system requirements, see [Requirements](https://doc.ibexa.co/en/5.0/getting_started/requirements/). #### Symfony 7.3 With this release, Ibexa DXP moves to Symfony 7.3 from the previously used versions of Symfony. For details, see [Symfony 7.3](https://symfony.com/blog/symfony-7-3-curated-new-features). #### Doctrine DBAL 3.9 By moving to Doctrine DBAL 3.9, Ibexa DXP brings developers better performance, cleaner code, and stronger foundation for a more modern and maintainable application. #### PHP 8.3 With performance, coding safety and security in mind, with this version, Ibexa DXP moves to [PHP 8.3](https://www.php.net/releases/8.3/en.php) and drops support for lower versions of the language. #### OpenAPI support Adding support for generating the [OpenAPI](https://www.openapis.org/) specification for our REST API makes future changes more manageable, and helps our partners automatically generate REST API clients. For more information, see [REST API usage](https://doc.ibexa.co/en/5.0/api/rest_api/rest_api_usage/rest_api_usage/#openapi-support). Support for serialization and deserialization of REST payloads with the [Symfony Serializer](https://symfony.com/doc/current/serializer.html) component improves data reliability and simplifies debugging. #### React 19 Ibexa DXP's Back Office now uses [React 19](https://react.dev/blog/2024/12/05/react-19). This upgrade enhances maintainability, unlocks new UI capabilities, and simplifies future feature development. ### Developer experience #### New packages The following packages have been introduced in Ibexa DXP v5.0.0: - ibexa/collaboration - ibexa/connector-ai - ibexa/connector-openai - ibexa/discounts - ibexa/discounts-codes - ibexa/product-catalog-date-time-attribute - ibexa/product-catalog-symbol-attribute - ibexa/share #### REST APIs Ibexa DXP v5.0.0 adds REST API coverage for the following features: - AI Actions: - Action Configurations - Action Types - Discounts - Collaboration #### PHP API The PHP API has been expanded with the following classes and interfaces: AI Actions - [`Ibexa\Contracts\ConnectorAi\Action\Action`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-Action.html) - [`Ibexa\Contracts\ConnectorAi\Action\ActionContext`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-ActionContext.html) - [`Ibexa\Contracts\ConnectorAi\Action\ActionFactoryInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-ActionFactoryInterface.html) - [`Ibexa\Contracts\ConnectorAi\Action\ActionHandlerInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-ActionHandlerInterface.html) - [`Ibexa\Contracts\ConnectorAi\Action\ActionHandlerResolverInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-ActionHandlerResolverInterface.html) - [`Ibexa\Contracts\ConnectorAi\Action\GenerateAltTextAction`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-GenerateAltTextAction.html) - [`Ibexa\Contracts\ConnectorAi\Action\LLMBaseActionTypeInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-LLMBaseActionTypeInterface.html) - [`Ibexa\Contracts\ConnectorAi\Action\RefineTextAction`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-RefineTextAction.html) - [`Ibexa\Contracts\ConnectorAi\Action\RuntimeContext`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-RuntimeContext.html) - [`Ibexa\Contracts\ConnectorAi\ActionConfiguration\ActionConfigurationCreateStruct`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-ActionConfigurationCreateStruct.html) - [`Ibexa\Contracts\ConnectorAi\ActionConfiguration\ActionConfigurationCopyStruct`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-ActionConfigurationCopyStruct.html) - [`Ibexa\Contracts\ConnectorAi\ActionConfiguration\ActionConfigurationListInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-ActionConfigurationListInterface.html) - [`Ibexa\Contracts\ConnectorAi\ActionConfiguration\ActionConfigurationOptions`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-ActionConfigurationOptions.html) - [`Ibexa\Contracts\ConnectorAi\ActionConfiguration\ActionConfigurationQuery`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-ActionConfigurationQuery.html) - [`Ibexa\Contracts\ConnectorAi\ActionConfiguration\ActionConfigurationUpdateStruct`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-ActionConfigurationUpdateStruct.html) - [`Ibexa\Contracts\ConnectorAi\ActionConfiguration\ActionHandlerOptionsFormMapperInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-ActionHandlerOptionsFormMapperInterface.html) - [`Ibexa\Contracts\ConnectorAi\ActionConfiguration\ActionTypeOptionsFormMapperInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-ActionTypeOptionsFormMapperInterface.html) - [`Ibexa\Contracts\ConnectorAi\ActionConfiguration\OptionsFormatterInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-OptionsFormatterInterface.html) - [`Ibexa\Contracts\ConnectorAi\ActionType\ActionTypeFactoryInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionType-ActionTypeFactoryInterface.html) - [`Ibexa\Contracts\ConnectorAi\ActionType\ActionTypeInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionType-ActionTypeInterface.html) - [`Ibexa\Contracts\ConnectorAi\ActionType\ActionTypeRegistryInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionType-ActionTypeRegistryInterface.html) - [`Ibexa\Contracts\ConnectorAi\ActionType\OptionsValidatorError`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionType-OptionsValidatorError.html) - [`Ibexa\Contracts\ConnectorAi\ActionType\OptionsValidatorInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionType-OptionsValidatorInterface.html) - [`Ibexa\Contracts\ConnectorAi\ActionType\OptionsValidatorRegistryInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionType-OptionsValidatorRegistryInterface.html) - [`Ibexa\Contracts\ConnectorAi\ActionConfigurationInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfigurationInterface.html) - [`Ibexa\Contracts\ConnectorAi\ActionConfigurationServiceDecorator`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfigurationServiceDecorator.html) - [`Ibexa\Contracts\ConnectorAi\ActionConfigurationServiceInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfigurationServiceInterface.html) - [`Ibexa\Contracts\ConnectorAi\ActionHandlerRegistryInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionHandlerRegistryInterface.html) - [`Ibexa\Contracts\ConnectorAi\ActionInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionInterface.html) - [`Ibexa\Contracts\ConnectorAi\ActionResponseInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionResponseInterface.html) - [`Ibexa\Contracts\ConnectorAi\ActionServiceDecorator`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionServiceDecorator.html) - [`Ibexa\Contracts\ConnectorAi\ActionServiceInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionServiceInterface.html) - [`Ibexa\Contracts\ConnectorAi\AdapterAwareActionInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-AdapterAwareActionInterface.html) - [`Ibexa\Contracts\ConnectorAi\DataType`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-DataType.html) - [`Ibexa\Contracts\ConnectorAi\PromptResolverInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-PromptResolverInterface.html) - [`Ibexa\Contracts\ConnectorAi\Prompt\PromptFactory`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Prompt-PromptFactory.html) - [`Ibexa\Contracts\ConnectorAi\Prompt\PromptInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Prompt-PromptInterface.html) - [`Ibexa\Contracts\ConnectorAi\PromptResolverInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-PromptResolverInterface.html) - [`Ibexa\Contracts\ConnectorOpenAi\ClientProviderInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorOpenAi-ClientProviderInterface.html) Discounts - [`Ibexa\Contracts\Discounts\DiscountConditionCriterionMapperInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountConditionCriterionMapperInterface.html) - [`Ibexa\Contracts\Discounts\DiscountFormMapperInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountFormMapperInterface.html) - [`Ibexa\Contracts\Discounts\DiscountPrioritizationStrategyInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountPrioritizationStrategyInterface.html) - [`Ibexa\Contracts\Discounts\DiscountServiceDecorator`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountServiceDecorator.html) - [`Ibexa\Contracts\Discounts\DiscountServiceInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountServiceInterface.html) - [`Ibexa\Contracts\Discounts\DiscountValueFormatterInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountValueFormatterInterface.html) - [`Ibexa\Contracts\Discounts\DiscountVariablesResolverInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountVariablesResolverInterface.html) - [`Ibexa\Contracts\Discounts\Admin\Form\DiscountValueFormTypeMapperInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-Form-DiscountValueFormTypeMapperInterface.html) - [`Ibexa\Contracts\Discounts\Admin\Form\FormThemeProviderInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-Form-FormThemeProviderInterface.html) - [`Ibexa\Contracts\Discounts\Admin\FormMapper\ConditionsMapperInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-FormMapper-ConditionsMapperInterface.html) - [`Ibexa\Contracts\Discounts\Admin\FormMapper\DiscountValueMapperInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-FormMapper-DiscountValueMapperInterface.html) - [`Ibexa\Contracts\Discounts\Admin\FormMapper\GeneralPropertiesMapperInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-FormMapper-GeneralPropertiesMapperInterface.html) - [`Ibexa\Contracts\Discounts\Admin\FormMapper\ProductConditionsMapperInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-FormMapper-ProductConditionsMapperInterface.html) - [`Ibexa\Contracts\Discounts\Admin\FormMapper\StepDataObjectMapperInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-FormMapper-StepDataObjectMapperInterface.html) - [`Ibexa\Contracts\Discounts\Admin\FormMapper\UserConditionsMapperInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-FormMapper-UserConditionsMapperInterface.html) - [`Ibexa\Contracts\Discounts\Exception\DiscountConditionNotFoundException`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Exception-DiscountConditionNotFoundException.html) - [`Ibexa\Contracts\Discounts\Exception\DiscountExpressionInvalidArgumentException`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Exception-DiscountExpressionInvalidArgumentException.html) - [`Ibexa\Contracts\Discounts\Exception\DiscountExpressionRuntimeException`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Exception-DiscountExpressionRuntimeException.html) - [`Ibexa\Contracts\Discounts\Exception\DiscountNotFoundException`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Exception-DiscountNotFoundException.html) - [`Ibexa\Contracts\Discounts\Exception\DiscountRuleNotFoundException`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Exception-DiscountRuleNotFoundException.html) - [`Ibexa\Contracts\Discounts\Exception\DiscountValueResolutionException`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Exception-DiscountValueResolutionException.html) - [`Ibexa\Contracts\Discounts\Policy\AbstractDiscountPolicy`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Policy-AbstractDiscountPolicy.html) - [`Ibexa\Contracts\Discounts\Policy\Create`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Policy-Create.html) - [`Ibexa\Contracts\Discounts\Policy\Delete`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Policy-Delete.html) - [`Ibexa\Contracts\Discounts\Policy\Disable`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Policy-Disable.html) - [`Ibexa\Contracts\Discounts\Policy\Enable`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Policy-Enable.html) - [`Ibexa\Contracts\Discounts\Policy\Update`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Policy-Update.html) - [`Ibexa\Contracts\Discounts\Policy\View`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Policy-View.html) - [`Ibexa\Contracts\Discounts\Value\CartDiscountConditionInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-CartDiscountConditionInterface.html) - [`Ibexa\Contracts\Discounts\Value\DiscountConditionInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-DiscountConditionInterface.html) - [`Ibexa\Contracts\Discounts\Value\DiscountExpressionAwareInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-DiscountExpressionAwareInterface.html) - [`Ibexa\Contracts\Discounts\Value\DiscountInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-DiscountInterface.html) - [`Ibexa\Contracts\Discounts\Value\DiscountListInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-DiscountListInterface.html) - [`Ibexa\Contracts\Discounts\Value\DiscountRuleInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-DiscountRuleInterface.html) - [`Ibexa\Contracts\Discounts\Value\DiscountTranslationInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-DiscountTranslationInterface.html) - [`Ibexa\Contracts\Discounts\Value\DiscountType`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-DiscountType.html) - [`Ibexa\Contracts\Discounts\Value\DiscountValueInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-DiscountValueInterface.html) - [`Ibexa\Contracts\Discounts\Value\Struct\DiscountCreateStruct`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Struct-DiscountCreateStruct.html) - [`Ibexa\Contracts\Discounts\Value\Struct\DiscountStructInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Struct-DiscountStructInterface.html) - [`Ibexa\Contracts\Discounts\Value\DiscountTranslationStruct`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Struct-DiscountTranslationStruct.html) - [`Ibexa\Contracts\Discounts\Value\DiscountUpdateStruct`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Struct-DiscountUpdateStruct.html) - [`Ibexa\Contracts\Discounts\Value\TranslationAwareDiscountStructInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Struct-TranslationAwareDiscountStructInterface.html) - [`Ibexa\Contracts\Discounts\Value\TranslationAwareDiscountStructTrait`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Struct-TranslationAwareDiscountStructTrait.html) - [`Ibexa\Contracts\DiscountsCodes\Exception\DiscountCodeNotFoundException`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-DiscountsCodes-Exception-DiscountCodeNotFoundException.html) - [`Ibexa\Contracts\DiscountsCodes\Exception\DiscountCodeRateLimitExceededException`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-DiscountsCodes-Exception-DiscountCodeRateLimitExceededException.html) - [`Ibexa\Contracts\DiscountsCodes\Exception\DiscountCodeUnusableException`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-DiscountsCodes-Exception-DiscountCodeUnusableException.html) - [`Ibexa\Contracts\DiscountsCodes\Exception\DiscountCodeUserInvalidArgumentException`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-DiscountsCodes-Exception-DiscountCodeUserInvalidArgumentException.html) - [`Ibexa\Contracts\DiscountsCodes\Value\DiscountCodeUsageInterface`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-DiscountsCodes-Value-DiscountCodeUsageInterface.html) - [`Ibexa\Contracts\DiscountsCodes\Value\DiscountCodeUser`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-DiscountsCodes-Value-DiscountCodeUser.html) - [`Ibexa\Contracts\DiscountsCodes\Value\Query\DiscountCodeUsageQuery`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-DiscountsCodes-Value-Query-DiscountCodeUsageQuery.html) - [`Ibexa\Contracts\DiscountsCodes\Value\Struct\DiscountCodeCreateStruct`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-DiscountsCodes-Value-Struct-DiscountCodeCreateStruct.html) - [`Ibexa\Contracts\DiscountsCodes\Value\StructDiscountCodeUpdateStruct`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-DiscountsCodes-Value-Struct-DiscountCodeUpdateStruct.html) Product catalog attributes - [`Ibexa\Contracts\ProductCatalogDateTimeAttribute`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/namespaces/ibexa-contracts-productcatalogdatetimeattribute.html) - [`Ibexa\Contracts\ProductCatalogSymbolAttribute`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/namespaces/ibexa-contracts-productcatalogsymbolattribute.html) #### Search Criteria The following search criteria have been added in the v5.0 release: AI Actions - [`Ibexa\Contracts\ConnectorAi\ActionConfiguration\Query\Criterion\Enabled`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-Query-Criterion-Enabled.html) - [`Ibexa\Contracts\ConnectorAi\ActionConfiguration\Query\Criterion\Identifier`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-Query-Criterion-Identifier.html) - [`Ibexa\Contracts\ConnectorAi\ActionConfiguration\Query\Criterion\LogicalAnd`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-Query-Criterion-LogicalAnd.html) - [`Ibexa\Contracts\ConnectorAi\ActionConfiguration\Query\Criterion\LogicalOr`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-Query-Criterion-LogicalOr.html) - [`Ibexa\Contracts\ConnectorAi\ActionConfiguration\Query\Criterion\Name`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-Query-Criterion-Name.html) - [`Ibexa\Contracts\ConnectorAi\ActionConfiguration\Query\Criterion\Type`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-Query-Criterion-Type.html) Discounts - [`Ibexa\Contracts\Discounts\Value\Query\Criterion\CreatedAtCriterion`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-CreatedAtCriterion.html) - [`Ibexa\Contracts\Discounts\Value\Query\Criterion\CreatorCriterion`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-CreatorCriterion.html) - [`Ibexa\Contracts\Discounts\Value\Query\Criterion\EndDateCriterion`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-EndDateCriterion.html) - [`Ibexa\Contracts\Discounts\Value\Query\Criterion\IdentifierCriterion`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-IdentifierCriterion.html) - [`Ibexa\Contracts\Discounts\Value\Query\Criterion\IsEnabledCriterion`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-IsEnabledCriterion.html) - [`Ibexa\Contracts\Discounts\Value\Query\Criterion\LogicalAnd`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-LogicalAnd.html) - [`Ibexa\Contracts\Discounts\Value\Query\Criterion\LogicalOr`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-LogicalOr.html) - [`Ibexa\Contracts\Discounts\Value\Query\Criterion\NameCriterion`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-NameCriterion.html) - [`Ibexa\Contracts\Discounts\Value\Query\Criterion\PriorityCriterion`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-PriorityCriterion.html) - [`Ibexa\Contracts\Discounts\Value\Query\Criterion\StartDateCriterion`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-StartDateCriterion.html) - [`Ibexa\Contracts\Discounts\Value\Query\Criterion\TypeCriterion`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-TypeCriterion.html) Product catalog attributes - [`Ibexa\Contracts\ProductCatalogDateTimeAttribute\Search\Criterion\DateTimeAttribute`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalogDateTimeAttribute-Search-Criterion-DateTimeAttribute.html) - [`Ibexa\Contracts\ProductCatalogDateTimeAttribute\Search\Criterion\DateTimeAttributeRange`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalogDateTimeAttribute-Search-Criterion-DateTimeAttributeRange.html) - [`Ibexa\Contracts\ProductCatalogSymbolAttribute\Search\Criterion\SymbolAttribute`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalogSymbolAttribute-Search-Criterion-SymbolAttribute.html) #### Sort Clauses The following sort clauses have been added in the v5.0 release: AI Actions - [`Ibexa\Contracts\ConnectorAi\ActionConfiguration\Query\SortClause\Enabled`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-Query-SortClause-Enabled.html) - [`Ibexa\Contracts\ConnectorAi\ActionConfiguration\Query\SortClause\Id`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-Query-SortClause-Id.html) - [`Ibexa\Contracts\ConnectorAi\ActionConfiguration\Query\SortClause\Identifier`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-Query-SortClause-Identifier.html) Discounts - [`Ibexa\Contracts\Discounts\Value\Query\SortClause\CreatedAt`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-SortClause-CreatedAt.html) - [`Ibexa\Contracts\Discounts\Value\Query\SortClause\EndDate`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-SortClause-EndDate.html) - [`Ibexa\Contracts\Discounts\Value\Query\SortClause\Id`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-SortClause-Id.html) - [`Ibexa\Contracts\Discounts\Value\Query\SortClause\Identifier`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-SortClause-Identifier.html) - [`Ibexa\Contracts\Discounts\Value\Query\SortClause\Priority`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-SortClause-Priority.html) - [`Ibexa\Contracts\Discounts\Value\Query\SortClause\StartDate`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-SortClause-StartDate.html) - [`Ibexa\Contracts\Discounts\Value\Query\SortClause\Type`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-SortClause-Type.html) - [`Ibexa\Contracts\Discounts\Value\Query\SortClause\UpdatedAt`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-SortClause-UpdatedAt.html) #### Events The following events have been added in the v5.0 release: AI Actions - [`\Ibexa\Contracts\ConnectorAi\Action\Event\BeforeExecuteEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-Event-BeforeExecuteEvent.html) - [`\Ibexa\Contracts\ConnectorAi\Action\Event\ExecuteEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-Event-ExecuteEvent.html) - [`\Ibexa\Contracts\ConnectorAi\ActionConfiguration\Event\BeforeCreateActionConfigurationEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-Event-BeforeCreateActionConfigurationEvent.html) - [`\Ibexa\Contracts\ConnectorAi\ActionConfiguration\Event\CreateActionConfigurationEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-Event-CreateActionConfigurationEvent.html) - [`\Ibexa\Contracts\ConnectorAi\ActionConfiguration\Event\BeforeUpdateActionConfigurationEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-Event-BeforeUpdateActionConfigurationEvent.html) - [`\Ibexa\Contracts\ConnectorAi\ActionConfiguration\Event\UpdateActionConfigurationEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-Event-UpdateActionConfigurationEvent.html) - [`\Ibexa\Contracts\ConnectorAi\ActionConfiguration\Event\BeforeDeleteActionConfigurationEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-Event-BeforeDeleteActionConfigurationEvent.html) - [`\Ibexa\Contracts\ConnectorAi\ActionConfiguration\Event\DeleteActionConfigurationEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-Event-DeleteActionConfigurationEvent.html) - [`Ibexa\Contracts\ConnectorAi\Events\ContextEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Events-ContextEvent.html) - [`Ibexa\Contracts\ConnectorAi\Events\ResolveActionConfigurationWidgetConfigEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Events-ResolveActionConfigurationWidgetConfigEvent.html) - [`Ibexa\Contracts\ConnectorAi\Events\ResolveActionHandlerEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Events-ResolveActionHandlerEvent.html) Discounts - [`\Ibexa\Contracts\Discounts\Event\BeforeCreateDiscountEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-BeforeCreateDiscountEvent.html) - [`\Ibexa\Contracts\Discounts\Event\CreateDiscountEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-CreateDiscountEvent.html) - [`\Ibexa\Contracts\Discounts\Event\BeforeDeleteDiscountEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-BeforeDeleteDiscountEvent.html) - [`\Ibexa\Contracts\Discounts\Event\DeleteDiscountEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-DeleteDiscountEvent.html) - [`\Ibexa\Contracts\Discounts\Event\BeforeUpdateDiscountEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-BeforeUpdateDiscountEvent.html) - [`\Ibexa\Contracts\Discounts\Event\UpdateDiscountEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-UpdateDiscountEvent.html) - [`\Ibexa\Contracts\Discounts\Event\CreateDiscountCreateStructEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-CreateDiscountCreateStructEvent.html) - [`\Ibexa\Contracts\Discounts\Event\CreateDiscountUpdateStructEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-CreateDiscountUpdateStructEvent.html) - [`\Ibexa\Contracts\Discounts\Event\CreateFormDataEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-CreateFormDataEvent.html) - [`\Ibexa\Contracts\Discounts\Event\MapDiscountToFormDataEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-MapDiscountToFormDataEvent.html) - [`\Ibexa\Contracts\Discounts\Event\Step\CreateFormDataEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-CreateFormDataEvent.html) - [`\Ibexa\Contracts\Discounts\Event\Step\MapCreateDataToStructEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-Step-MapCreateDataToStructEvent.html) - [`\Ibexa\Contracts\Discounts\Event\Step\MapDiscountToFormDataEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-MapDiscountToFormDataEvent.html) - [`\Ibexa\Contracts\Discounts\Event\Step\MapUpdateDataToStructEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-Step-MapUpdateDataToStructEvent.html) - [`\Ibexa\Contracts\Discounts\Admin\Form\Event\PreDiscountCreateEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-Form-Event-PreDiscountCreateEvent.html) - [`\Ibexa\Contracts\DiscountsCodes\Event\BeforeDiscountCodeApplyEvent`](https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-DiscountsCodes-Event-BeforeDiscountCodeApplyEvent.html) #### Twig functions The following Twig functions have been added in the v5.0 release: - [`ibexa_ai_config`](https://doc.ibexa.co/en/5.0/templating/twig_function_reference/ai_actions_twig_functions#ibexa_ai_config) - [`ibexa_render_discount_rule_type`](https://doc.ibexa.co/en/5.0/templating/twig_function_reference/discounts_twig_functions#ibexa_render_discount_rule_type) - [`ibexa_discounts_render_discount_badge`](https://doc.ibexa.co/en/5.0/templating/twig_function_reference/discounts_twig_functions#ibexa_discounts_render_discount_badge) - [`ibexa_get_original_price`](https://doc.ibexa.co/en/5.0/templating/twig_function_reference/discounts_twig_functions#ibexa_get_original_price) - [`ibexa_format_discount_value`](https://doc.ibexa.co/en/5.0/templating/twig_function_reference/discounts_twig_functions#ibexa_format_discount_value) - [`ibexa_discounts_is_active`](https://doc.ibexa.co/en/5.0/templating/twig_function_reference/discounts_twig_functions#ibexa_discounts_is_active) - [`ibexa_discounts_form_themes`](https://doc.ibexa.co/en/5.0/templating/twig_function_reference/discounts_twig_functions#ibexa_discounts_form_themes) - [`ibexa_discounts_can_edit`](https://doc.ibexa.co/en/5.0/templating/twig_function_reference/discounts_twig_functions#ibexa_discounts_can_edit) - [`ibexa_discounts_can_enable`](https://doc.ibexa.co/en/5.0/templating/twig_function_reference/discounts_twig_functions#ibexa_discounts_can_enable) - [`ibexa_discounts_can_disable`](https://doc.ibexa.co/en/5.0/templating/twig_function_reference/discounts_twig_functions#ibexa_discounts_can_disable) - [`ibexa_discounts_can_delete`](https://doc.ibexa.co/en/5.0/templating/twig_function_reference/discounts_twig_functions#ibexa_discounts_can_delete) #### Other upgrades This release brings other minor upgrades intended to improve the developer's experience: - To improve code clarity, reliability, and error detection, type hint declarations that specify the expected data type have been added in multiple places throughout the product - In anticipation of [changes coming with PHP 8.4](https://php.watch/versions/8.4/implicitly-marking-parameter-type-nullable-deprecated), implicit nullable type declarations have been replaced with nullable type declarations throughout the product code. It is recommended that you update your custom code in the same way - Developer experience has improved with capabilities offered by PHP in version 8.3. For example, the `AsTwigComponent` attribute [facilitates autoconfiguration](https://doc.ibexa.co/en/5.0/templating/components/#php-code) of Twig components - With protection against breaking changes and easier refactoring in mind, [TypeScript](https://www.typescriptlang.org/) can now be used to extend the Back Office - [Ibexa Rector package](https://github.com/ibexa/rector) has been introduced that is based on [Rector](https://github.com/rectorphp) and comes with additional rules for working with Ibexa code. You can use it to get rid of PHP code deprecations - [New icons](https://doc.ibexa.co/en/5.0/templating/twig_function_reference/icon_twig_functions#icons-reference) replace the ones found in previous versions and serve as a highlight of a future system design ### Deprecations Refer to [Ibexa DXP v5.0 renames, deprecations and removals](https://doc.ibexa.co/en/5.0/release_notes/ibexa_dxp_v5.0_deprecations/) for a full list of changes and how they influence your project. ### Full changelog To learn more about all the included changes, see the full release change logs: - [Ibexa Headless v5.0.0](https://github.com/ibexa/headless/releases/tag/v5.0.0) - [Ibexa Experience v5.0.0](https://github.com/ibexa/experience/releases/tag/v5.0.0) - [Ibexa Commerce v5.0.0](https://github.com/ibexa/commerce/releases/tag/v5.0.0) To update your application, see the [update instructions](https://doc.ibexa.co/en/5.0/update_and_migration/from_4.6/update_to_5.0/). # Ibexa DXP v5.0 renames, deprecations and removals This page lists backwards compatibility breaks and deprecations introduced in Ibexa DXP v5.0. > **Tip: Upgrade to v5** > > For a guide on moving your project to v5.0, see [Update and migration instructions](https://doc.ibexa.co/en/latest/update_and_migration/from_4.6/update_to_5.0/index.md). Ibexa DXP v5.0 introduces further modifications to significant parts of the code to align with the ones introduced in previous versions. These changes include dropped packages, changing database table and column names, field identifiers, namespaces, function names, and others. ## Dropped packages Ibexa DXP v5.0 no longer includes legacy Commerce packages. The solution has been replaced with [Commerce](https://doc.ibexa.co/en/latest/commerce/commerce/index.md) that is included as standard and has been continuously developed since v4.4. Also, packages `compatibility-layer` and `icons` have been dropped. ## Database table and column names A number of database table and column names have changed. If your custom code directly queries them, you need to update the code. | Old name | New name | | ------------------------------------------------------- | ------------------------------------------------------------------------- | | `ezbinaryfile` | `ibexa_binary_file` | | `ezcobj_state` | `ibexa_object_state` | | `ezcobj_state_group` | `ibexa_object_state_group` | | `ezcobj_state_group_language` | `ibexa_object_state_group_language` | | `ezcobj_state_language` | `ibexa_object_state_language` | | `ezcobj_state_link` | `ibexa_object_state_link` | | `ezcontent_language` | `ibexa_content_language` | | `ezcontentbrowsebookmark` | `ibexa_content_bookmark` | | `ezcontentclass` | `ibexa_content_type` | | `ezcontentclass_attribute` | `ibexa_content_type_field_definition` | | `ezcontentclass_attribute.contentclass_id` | `ibexa_content_type_field_definition.content_type_id` | | `ezcontentclass_attribute_ml` | `ibexa_content_type_field_definition_ml` | | `ezcontentclass_attribute_ml.contentclass_attribute_id` | `ibexa_content_type_field_definition_ml.content_type_field_definition_id` | | `ezcontentclass_classgroup` | `ibexa_content_type_group_assignment` | | `ezcontentclass_classgroup.contentclass_id` | `ibexa_content_type_group_assignment.content_type_id` | | `ezcontentclass_name` | `ibexa_content_type_name` | | `ezcontentclass_name.contentclass_id` | `ibexa_content_type_name.content_type_id` | | `ezcontentclassgroup` | `ibexa_content_type_group` | | `ezcontentobject` | `ibexa_content` | | `ezcontentobject.contentclass_id` | `ibexa_content.content_type_id` | | `ezcontentobject_attribute` | `ibexa_content_field` | | `ezcontentobject_attribute.contentclassattribute_id` | `ibexa_content_field.content_type_field_definition_id` | | `ezcontentobject_link` | `ibexa_content_relation` | | `ezcontentobject_link.contentclassattribute_id` | `ibexa_content_relation.content_type_field_definition_id` | | `ezcontentobject_name` | `ibexa_content_name` | | `ezcontentobject_trash` | `ibexa_content_trash` | | `ezcontentobject_tree` | `ibexa_content_tree` | | `ezcontentobject_version` | `ibexa_content_version` | | `ezdatebasedpublisher_scheduled_entries` | `ibexa_scheduler_scheduled_entries` | | `ezdfsfile` | `ibexa_dfs_file` | | `ezeditorialworkflow_markings` | `ibexa_workflow_markings` | | `ezeditorialworkflow_transitions` | `ibexa_workflow_transitions` | | `ezeditorialworkflow_workflows` | `ibexa_workflow_workflows` | | `ezform_field_attributes` | `ibexa_form_field_attributes` | | `ezform_field_validators` | `ibexa_form_field_validators` | | `ezform_fields` | `ibexa_form_fields` | | `ezform_form_submission_data` | `ibexa_form_form_submission_data` | | `ezform_form_submissions` | `ibexa_form_form_submissions` | | `ezform_forms` | `ibexa_form_forms` | | `ezgmaplocation` | `ibexa_map_location` | | `ezimagefile` | `ibexa_image_file` | | `ezkeyword` | `ibexa_keyword` | | `ezkeyword_attribute_link` | `ibexa_keyword_field_link` | | `ezmedia` | `ibexa_media` | | `eznode_assignment` | `ibexa_node_assignment` | | `eznotification` | `ibexa_notification` | | `ezpackage` | `ibexa_package` | | `ezpage_attributes` | `ibexa_page_attributes` | | `ezpage_blocks` | `ibexa_page_blocks` | | `ezpage_blocks_design` | `ibexa_page_blocks_design` | | `ezpage_blocks_visibility` | `ibexa_page_blocks_visibility` | | `ezpage_map_attributes_blocks` | `ibexa_page_map_attributes_blocks` | | `ezpage_map_blocks_zones` | `ibexa_page_map_blocks_zones` | | `ezpage_map_zones_pages` | `ibexa_page_map_zones_pages` | | `ezpage_pages` | `ibexa_page_pages` | | `ezpage_zones` | `ibexa_page_zones` | | `ezpolicy` | `ibexa_policy` | | `ezpolicy_limitation` | `ibexa_policy_limitation` | | `ezpolicy_limitation_value` | `ibexa_policy_limitation_value` | | `ezpreferences` | `ibexa_preferences` | | `ezrole` | `ibexa_role` | | `ezsearch_object_word_link` | `ibexa_search_object_word_link` | | `ezsearch_object_word_link.contentclass_id` | `ibexa_search_object_word_link.content_type_id` | | `ezsearch_object_word_link.contentclass_attribute_id` | `ibexa_search_object_word_link.content_type_field_definition_id` | | `ezsearch_word` | `ibexa_search_word` | | `ezsection` | `ibexa_section` | | `ezsite` | `ibexa_site` | | `ezsite_data` | `ibexa_site_data` | | `ezsite_public_access` | `ibexa_site_public_access` | | `ezurl` | `ibexa_url` | | `ezurl_object_link` | `ibexa_url_content_link` | | `ezurlalias` | `ibexa_url_alias` | | `ezurlalias_ml` | `ibexa_url_alias_ml` | | `ezurlalias_ml_incr` | `ibexa_url_alias_ml_incr` | | `ezurlwildcard` | `ibexa_url_wildcard` | | `ezuser` | `ibexa_user` | | `ezuser_accountkey` | `ibexa_user_accountkey` | | `ezuser_role` | `ibexa_user_role` | | `ezuser_setting` | `ibexa_user_setting` | ## Field type identifiers Several field type identifiers have changed. | Old identifier (`legacy_alias`) | New identifier (`alias`) | | ------------------------------- | ---------------------------- | | `ezauthor` | `ibexa_author` | | `ezbinaryfile` | `ibexa_binaryfile` | | `ezboolean` | `ibexa_boolean` | | `ezcontentquery` | `ibexa_content_query` | | `ezcountry` | `ibexa_country` | | `ezdate` | `ibexa_date` | | `ezdatetime` | `ibexa_datetime` | | `ezemail` | `ibexa_email` | | `ezfloat` | `ibexa_float` | | `ezform` | `ibexa_form` | | `ezgmaplocation` | `ibexa_gmap_location` | | `ezimage` | `ibexa_image` | | `ezimageasset` | `ibexa_image_asset` | | `ezinteger` | `ibexa_integer` | | `ezisbn` | `ibexa_isbn` | | `ezkeyword` | `ibexa_keyword` | | `ezlandingpage` | `ibexa_landing_page` | | `ezmatrix` | `ibexa_matrix` | | `ezmedia` | `ibexa_media` | | `ezobjectrelation` | `ibexa_object_relation` | | `ezobjectrelationlist` | `ibexa_object_relation_list` | | `ezrichtext` | `ibexa_richtext` | | `ezselection` | `ibexa_selection` | | `ezstring` | `ibexa_string` | | `eztext` | `ibexa_text` | | `eztime` | `ibexa_time` | | `ezurl` | `ibexa_url` | | `ezuser` | `ibexa_user` | ## PHP API classes and methods > **Note: Ibexa Rector** > > [Ibexa Rector package](https://github.com/ibexa/rector) has been introduced that is based on [Rector](https://github.com/rectorphp) and comes with additional rules for working with Ibexa code. You can use it to get rid of PHP code deprecations. ### `ibexa/admin-ui` | Old FQN | New FQN / Comment | | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | | `\Ibexa\Contracts\AdminUi\Permission\PermissionCheckerInterface::getContentCreateLimitations` | `\Ibexa\AdminUi\Permission\LimitationResolverInterface::getContentCreateLimitations` | | `\Ibexa\Contracts\AdminUi\Permission\PermissionCheckerInterface::getContentUpdateLimitations` | `\Ibexa\AdminUi\Permission\LimitationResolverInterface::getContentUpdateLimitations` | | `\Ibexa\Contracts\AdminUi\UniversalDiscovery\Provider::getRestFormat` | Removed | | `\Ibexa\AdminUi\Form\Type\Search\DateIntervalType` | `\Ibexa\AdminUi\Form\Type\Date\DateIntervalType` | | `\Ibexa\AdminUi\Siteaccess\SiteaccessResolverInterface::getSiteaccessesForLocation` | `\Ibexa\AdminUi\Siteaccess\SiteaccessResolverInterface::getSiteAccessesList` | | `\Ibexa\AdminUi\Siteaccess\SiteaccessResolverInterface::getSiteaccesses` | `\Ibexa\AdminUi\Siteaccess\SiteaccessResolverInterface::getSiteAccessesList` | | `\Ibexa\AdminUi\Specification\AbstractSpecification` | `\Ibexa\Contracts\Core\Specification\AbstractSpecification` | | `\Ibexa\AdminUi\Specification\AndSpecification` | `\Ibexa\Contracts\Core\Specification\AndSpecification` | | `\Ibexa\AdminUi\Specification\NotSpecification` | `\Ibexa\Contracts\Core\Specification\NotSpecification` | | `\Ibexa\AdminUi\Specification\OrSpecification` | `\Ibexa\Contracts\Core\Specification\OrSpecification` | | `\Ibexa\AdminUi\Specification\SpecificationInterface` | `\Ibexa\Contracts\Core\Specification\SpecificationInterface` | | `\Ibexa\AdminUi\Tab\Dashboard\PagerContentToDataMapper` | `\Ibexa\AdminUi\Tab\Dashboard\PagerLocationToDataMapper` | | `\Ibexa\AdminUi\Translation\Extractor\LimitationTranslationExtractor` | Removed | | `\Ibexa\AdminUi\Translation\Extractor\PolicyTranslationExtractor` | Removed | | `\Ibexa\AdminUi\UI\Dataset\ContentDraftsDataset` | `\Ibexa\AdminUi\UI\Dataset\ContentDraftListDataset` | | `\Ibexa\AdminUi\UI\Dataset\DatasetFactory::relations` | `\Ibexa\AdminUi\UI\Dataset\DatasetFactory::relationList` | | `\Ibexa\AdminUi\UI\Dataset\DatasetFactory::contentDrafts` | `\Ibexa\AdminUi\UI\Dataset\DatasetFactory::contentDraftList` | | `\Ibexa\AdminUi\UI\Value\ValueFactory::createRelation` | `\Ibexa\AdminUi\UI\Value\ValueFactory::createRelationItem` | | `\Ibexa\AdminUi\Validator\ValidationErrorsProcessor` | `\Ibexa\ContentForms\Validator\ValidationErrorsProcessor` | | `\Ibexa\AdminUi\Validator\Constraints\FieldTypeValidator` | `\Ibexa\ContentForms\Validator\Constraints\FieldTypeValidator` | ### `ibexa/cart` | Old FQN | New FQN / Comment | | -------------------------------- | ---------------------------------------------- | | `\Ibexa\Cart\Money\MoneyFactory` | `\Ibexa\ProductCatalog\Money\IntlMoneyFactory` | ### `ibexa/content-forms` | Old FQN | New FQN / Comment | | -------------------------------------------------------------- | ------------------------------------------------------ | | `\Ibexa\Bundle\ContentForms\Controller\UserRegisterController` | `\Ibexa\Bundle\User\Controller\UserRegisterController` | | `\Ibexa\ContentForms\User\View\UserRegisterConfirmView` | `\Ibexa\User\View\UserRegisterConfirmView` | | `\Ibexa\ContentForms\User\View\UserRegisterFormView` | `\Ibexa\User\View\UserRegisterFormView` | ### `ibexa/core` Support for facet search has been dropped, use the `Aggregation` API instead. | Old FQN | New FQN / Comment | | ----------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | | `\Ibexa\Bundle\Core\DependencyInjection\Security\PolicyProvider\RepositoryPolicyProvider` | Removed | | `\Ibexa\Bundle\Core\Imagine\VariationPathGenerator` | `\Ibexa\Contracts\Core\Variation\VariationPathGenerator` | | `\Ibexa\ContentForms\User\View\UserRegisterFormView` | `\Ibexa\User\View\UserRegisterFormView` | | `/Ibexa\Bundle\Debug\Collector\PersistenceCacheCollector::getCount` | `\Ibexa\Bundle\Debug\Collector\PersistenceCacheCollector::getStats` | | `\Ibexa\Bundle\RepositoryInstaller\Installer\Installer::createConfiguration` | Deprecated | | `\Ibexa\Contracts\Core\FieldType\FieldStorage::getIndexData` | `\Ibexa\Contracts\Core\FieldType\Indexable` | | `\Ibexa\Contracts\Core\FieldType\BinaryBase\PathGenerator` | `\Ibexa\Contracts\Core\FieldType\BinaryBase\PathGeneratorInterface` | | `\Ibexa\Contracts\Core\IO\BinaryFile::$mimeType` | `\Ibexa\Core\IO\IOMetadataHandler::getMimeType` | | `\Ibexa\Contracts\Core\Persistence\Handler::beginTransaction` | `\Ibexa\Contracts\Core\Persistence\TransactionHandler::beginTransaction` | | `\Ibexa\Contracts\Core\Persistence\Handler::commit` | `\Ibexa\Contracts\Core\Persistence\TransactionHandler::commit` | | `\Ibexa\Contracts\Core\Persistence\Handler::rollback` | `\Ibexa\Contracts\Core\Persistence\TransactionHandler::rollback` | | `\Ibexa\Contracts\Core\Persistence\Bookmark\Bookmark::$name` | Removed | | `\Ibexa\Contracts\Core\Persistence\Bookmark\CreateStruct::$name` | Removed | | `\Ibexa\Contracts\Core\Persistence\Content\ContentInfo::STATUS_ARCHIVED` | `\Ibexa\Contracts\Core\Persistence\Content\ContentInfo::STATUS_TRASHED` | | `\Ibexa\Contracts\Core\Persistence\Content\ContentInfo::$isPublished` | Removed. Use `ContentInfo::$status` with value `STATUS_PUBLISHED`. | | `\Ibexa\Contracts\Core\Persistence\Content\LoadStruct` | Removed | | `\Ibexa\Contracts\Core\Persistence\Content\Location::$pathIdentificationString` | Removed | | `\Ibexa\Contracts\Core\Persistence\Content\Location\CreateStruct::$pathIdentificationString` | Removed | | `\Ibexa\Contracts\Core\Persistence\Content\Location\Handler::markSubtreeModified` | Removed | | `\Ibexa\Contracts\Core\Persistence\FieldType\IsEmptyValue` | Removed | | `\Ibexa\Contracts\Core\Persistence\User\Handler::loadPoliciesByUserId` | Removed | | `\Ibexa\Contracts\Core\Repository\ContentService::loadContentDrafts` | `\Ibexa\Contracts\Core\Repository\ContentService::loadContentDraftList` | | `\Ibexa\Contracts\Core\Repository\Values\Content\Location::SORT_FIELD_MODIFIED_SUBNODE` | Removed | | `\Ibexa\Contracts\Core\Repository\Values\Content\Query\FacetBuilder` | Removed. Use the `Aggregation` API. | | `\Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion\LogicalOperator::getSpecifications` | Removed | | `\Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion\Location\IsMainLocation::createFromQueryBuilder` | Removed. Use the constructor directly. | | `\Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion\Location\Priority::createFromQueryBuilder` | Removed. Use the constructor directly. | | `\Ibexa\Contracts\Core\Repository\Values\Content\Query\FacetBuilder\ContentTypeFacetBuilder` | Removed. Use the `Aggregation` API. | | `\Ibexa\Contracts\Core\Repository\Values\Content\Query\FacetBuilder\CriterionFacetBuilder` | Removed. Use the `Aggregation` API. | | `\Ibexa\Contracts\Core\Repository\Values\Content\Query\FacetBuilder\DateRangeFacetBuilder` | Removed. Use the `Aggregation` API. | | `\Ibexa\Contracts\Core\Repository\Values\Content\Query\FacetBuilder\FieldFacetBuilder` | Removed. Use the `Aggregation` API. | | `\Ibexa\Contracts\Core\Repository\Values\Content\Query\FacetBuilder\FieldRangeFacetBuilder` | Removed. Use the `Aggregation` API. | | `\Ibexa\Contracts\Core\Repository\Values\Content\Query\FacetBuilder\Location` | Removed. Use the `Aggregation` API. | | `\Ibexa\Contracts\Core\Repository\Values\Content\Query\FacetBuilder\LocationFacetBuilder` | Removed. Use the `Aggregation` API. | | `\Ibexa\Contracts\Core\Repository\Values\Content\Query\FacetBuilder\SectionFacetBuilder` | Removed. Use the `Aggregation` API. | | `\Ibexa\Contracts\Core\Repository\Values\Content\Query\FacetBuilder\TermFacetBuilder` | Removed. Use the `Aggregation` API. | | `\Ibexa\Contracts\Core\Repository\Values\Content\Query\FacetBuilder\UserFacetBuilder` | Removed. Use the `Aggregation` API. | | `\Ibexa\Contracts\Core\Repository\Values\Content\Query\FacetBuilder\Location\LocationFacetBuilder` | Removed. Use the `Aggregation` API. | | `\Ibexa\Contracts\Core\Repository\Values\Content\Search\Facet` | Removed. Use the `Aggregation` API. | | `\Ibexa\Contracts\Core\Repository\Values\Content\Search\SearchResult::$facets` | Removed. Use the `Aggregation` API. | | `\Ibexa\Contracts\Core\Repository\Values\Content\Search\SearchResult::$spellSuggestion` | `\Ibexa\Contracts\Core\Repository\Values\Content\Search\SearchResult::$spellcheck` | | `\Ibexa\Contracts\Core\Repository\Values\Content\Search\Facet\ContentTypeFacet` | Removed. Use the `Aggregation` API. | | `\Ibexa\Contracts\Core\Repository\Values\Content\Search\Facet\CriterionFacet` | Removed. Use the `Aggregation` API. | | `\Ibexa\Contracts\Core\Repository\Values\Content\Search\Facet\DateRangeFacet` | Removed. Use the `Aggregation` API. | | `\Ibexa\Contracts\Core\Repository\Values\Content\Search\Facet\FieldFacet` | Removed. Use the `Aggregation` API. | | `\Ibexa\Contracts\Core\Repository\Values\Content\Search\Facet\FieldRangeFacet` | Removed. Use the `Aggregation` API. | | `\Ibexa\Contracts\Core\Repository\Values\Content\Search\Facet\LocationFacet` | Removed. Use the `Aggregation` API. | | `\Ibexa\Contracts\Core\Repository\Values\Content\Search\Facet\RangeFacetEntry` | Removed. Use the `Aggregation` API. | | `\Ibexa\Contracts\Core\Repository\Values\Content\Search\Facet\SectionFacet` | Removed. Use the `Aggregation` API. | | `\Ibexa\Contracts\Core\Repository\Values\Content\Search\Facet\TermFacet` | Removed. Use the `Aggregation` API. | | `\Ibexa\Contracts\Core\Repository\Values\Content\Search\Facet\UserFacet` | Removed. Use the `Aggregation` API. | | `\Ibexa\Contracts\Core\Repository\Values\Content\Trash\SearchResult::$count` | `\Ibexa\Contracts\Core\Repository\Values\Content\Trash\SearchResult::$totalCount` | | `\Ibexa\Contracts\Core\Repository\Values\ContentType\ContentType::@$isContainer` | `\Ibexa\Contracts\Core\Repository\Values\ContentType\ContentType::isContainer` | | `\Ibexa\Contracts\Core\User\Identity` | Removed. Use the `FOSHttpCacheBundle` user context feature. | | `\Ibexa\Core\Event\UserService` | Listen to `BeforeUpdateUserPasswordEvent` instead of `BeforeUpdateUserEvent`. | | `\Ibexa\Core\Event\UserService` | Listen to `UpdateUserPasswordEvent` instead of `UpdateUserEvent`. | | `\Ibexa\Core\FieldType\GatewayBasedStorage` | `\Ibexa\Contracts\Core\FieldType\GatewayBasedStorage` | | `\Ibexa\Core\FieldType\StorageGateway` | `\Ibexa\Contracts\Core\FieldType\StorageGatewayInterface` | | `\Ibexa\Core\FieldType\Image\Value::@$path` | Equivalent to `$id` or `$inputUri`, depending on which one is set. | | `\Ibexa\Core\FieldType\Image\Value::fromString` | `\Ibexa\Core\FieldType\FieldType::acceptValue` | | `\Ibexa\Core\Helper\FieldHelper::getFieldDefinition` | If content exists, use `$content->getContentType()->getFieldDefinition($identifier)`. | | `\Ibexa\Core\Helper\PreviewLocationProvider::loadMainLocation` | `\Ibexa\Core\Helper\PreviewLocationProvider::loadMainLocationByContent` | | `\Ibexa\Core\IO\IOServiceInterface::getExternalPath` | `\Ibexa\Core\IO\IOServiceInterface::loadBinaryFileByUri` | | `\Ibexa\Core\IO\IOServiceInterface::getInternalPath` | Removed. Use the `uri` property. | | `\Ibexa\Core\IO\MetadataHandler` | Removed | | `\Ibexa\Core\IO\MetadataHandler\ImageSize` | Removed | | `\Ibexa\Core\IO\Values\BinaryFile::$mimeType` | `\Ibexa\Core\IO\IOServiceInterface::getMimeType` | | `\Ibexa\Core\MVC\Symfony\MVCEvents::CACHE_CLEAR_CONTENT` | Removed | | `\Ibexa\Core\MVC\Symfony\Event\ContentCacheClearEvent` | Removed | | `\Ibexa\Core\MVC\Symfony\Locale\LocaleConverterInterface::convertToEz` | `\Ibexa\Core\MVC\Symfony\Locale\LocaleConverterInterface::convertToRepository` | | `\Ibexa\Core\MVC\Symfony\SiteAccess\Matcher\Regex\Host` | Removed | | `\Ibexa\Core\MVC\Symfony\SiteAccess\Matcher\Regex\URI` | Removed | | `\Ibexa\Core\MVC\Symfony\View\Provider\Content` | Removed | | `\Ibexa\Core\MVC\Symfony\View\Provider\Location` | Removed | | `\Ibexa\Core\Persistence\Cache\PersistenceLogger::getCount` | `\Ibexa\Core\Persistence\Cache\PersistenceLogger::getStats` | | `\Ibexa\Core\Persistence\Legacy\Handler::beginTransaction` | Removed. Use `\Ibexa\Contracts\Core\Persistence\TransactionHandler\TransactionHandler::beginTransaction`. | | `\Ibexa\Core\Persistence\Legacy\Handler::commit` | Removed. Use `\Ibexa\Contracts\Core\Persistence\TransactionHandler\TransactionHandler::commit`. | | `\Ibexa\Core\Persistence\Legacy\Handler::rollback` | Removed. Use `\Ibexa\Contracts\Core\Persistence\TransactionHandler\TransactionHandler::rollback`. | | `\Ibexa\Core\Persistence\Legacy\Content\FieldValue\Converter\AuthorConverter::create` | Removed. Use the default constructor. | | `\Ibexa\Core\Persistence\Legacy\Content\FieldValue\Converter\BinaryFileConverter::create` | Removed. Use the default constructor. | | `\Ibexa\Core\Persistence\Legacy\Content\FieldValue\Converter\CheckboxConverter::create` | Removed. Use the default constructor. | | `\Ibexa\Core\Persistence\Legacy\Content\FieldValue\Converter\CountryConverter::create` | Removed. Use the default constructor. | | `\Ibexa\Core\Persistence\Legacy\Content\FieldValue\Converter\DateAndTimeConverter::create` | Removed. Use the default constructor. | | `\Ibexa\Core\Persistence\Legacy\Content\FieldValue\Converter` | Removed the `timestamp` property. | | `\Ibexa\Core\Persistence\Legacy\Content\FieldValue\DateConverter` | Removed the `timestamp` property. | | `\Ibexa\Core\Persistence\Legacy\Content\FieldValue\Converter\DateConverter::create` | Removed. Use the default constructor. | | `\Ibexa\Core\Persistence\Legacy\Content\FieldValue\Converter\EmailAddressConverter::create` | Removed. Use the default constructor. | | `\Ibexa\Core\Persistence\Legacy\Content\FieldValue\Converter\FloatConverter::create` | Removed. Use the default constructor. | | `\Ibexa\Core\Persistence\Legacy\Content\FieldValue\Converter\IntegerConverter::create` | Removed. Use the default constructor. | | `\Ibexa\Core\Persistence\Legacy\Content\FieldValue\Converter\ISBNConverter::create` | Removed. Use the default constructor. | | `\Ibexa\Core\Persistence\Legacy\Content\FieldValue\Converter\KeywordConverter::create` | Removed. Use the default constructor. | | `\Ibexa\Core\Persistence\Legacy\Content\FieldValue\Converter\MapLocationConverter::create` | Removed. Use the default constructor. | | `\Ibexa\Core\Persistence\Legacy\Content\FieldValue\Converter\MediaConverter::create` | Removed. Use the default constructor. | | `\Ibexa\Core\Persistence\Legacy\Content\FieldValue\Converter\NullConverter::create` | Removed. Use the default constructor. | | `\Ibexa\Core\Persistence\Legacy\Content\FieldValue\Converter\RelationConverter::create` | Removed. Use the default constructor. | | `\Ibexa\Core\Persistence\Legacy\Content\FieldValue\Converter\SelectionConverter::create` | Removed. Use the default constructor. | | `\Ibexa\Core\Persistence\Legacy\Content\FieldValue\Converter\TextBlockConverter::create` | Removed. Use the default constructor. | | `\Ibexa\Core\Persistence\Legacy\Content\FieldValue\Converter\TextLineConverter::create` | Removed. Use the default constructor. | | `\Ibexa\Core\Persistence\Legacy\Content\FieldValue\Converter\TimeConverter::create` | Removed. Use the default constructor. | | `\Ibexa\Core\Persistence\Legacy\Content\FieldValue\Converter\UrlConverter::create` | Removed. Use the default constructor. | | `\Ibexa\Core\Persistence\Legacy\Content\Language\MaskGenerator::generateLanguageMask` | `\Ibexa\Core\Persistence\Legacy\Content\Language\MaskGenerator::generateLanguageMaskFromLanguageCodes` | | `\Ibexa\Core\Repository\PermissionsCriterionHandler` | Removed | | `\Ibexa\Core\Repository\SectionService::countAssignedContents` | Deprecated. Use `SearchService` with `Section` criterion. | | `\Ibexa\Core\Repository\Helper\NameSchemaService` | `\Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface` | | `\Ibexa\Core\Repository\Helper\RoleDomainMapper` | Removed | | `\Ibexa\Core\Repository\Mapper\ContentTypeDomainMapper::buildSPIFieldDefinitionUpdate` | `\Ibexa\Core\Repository\Mapper\ContentTypeDomainMapper::buildSPIFieldDefinitionFromUpdateStruct` | | `\Ibexa\Core\Repository\Mapper\ContentTypeDomainMapper::buildSPIFieldDefinitionCreate` | `\Ibexa\Core\Repository\Mapper\ContentTypeDomainMapper::buildSPIFieldDefinitionFromCreateStruct` | | `\Ibexa\Core\Repository\User\PasswordHashServiceInterface` | `\Ibexa\Contracts\Core\Repository\PasswordHashService` | | `\Ibexa\Core\Search\Common\FieldNameResolver::getFieldNamesget` | `\Ibexa\Core\Search\Common\FieldNameResolver::getFieldTypes` | | `\Ibexa\Core\Search\Common\IncrementalIndexer::createSearchIndex` | Removed | | `\Ibexa\Tests\Integration\Core\Repository\BaseTest::isVersion4` | Removed | | `\Ibexa\Tests\Integration\Core\Repository\SearchServiceTest::testDeprecatedCriteriaProperty` | Removed | | `\Ibexa\Tests\Core\Repository\Service\Mock\PermissionsCriterionHandlerTest` | Removed | | `\Ibexa\Contracts\Core\Repository\Values\Translation` | Implementations must implement `\Stringable` interface. | | `\Ibexa\Bundle\Core\ApiLoader\RepositoryConfigurationProvider` | Deprecated. Use `\Ibexa\Contracts\Core\Container\ApiLoader\RepositoryConfigurationProviderInterface`. | | `\Ibexa\Bundle\Core\ApiLoader\RepositoryFactory` | Deprecated. Use `\Ibexa\Core\Base\Container\ApiLoader\RepositoryFactory`. | > **Note: Dropped single colon notation** > > Ibexa-named controllers can no longer be referenced using a single-colon notation. For example, `ibexa_content:viewAction` must be changed to `ibexa_content::viewAction`. > > The change affects the following controllers: > > - ibexa_content > - ibexa_query > - ibexa_query_render > - ibexa.controller.content.preview ### ibexa/corporate-account | Old FQN | New FQN / Comment | | ---------------------------------------------------------------------------------------------------------------- | ----------------- | | `\Ibexa\Bundle\CorporateAccount\Controller\ApplicationController\PersistenceCacheCollector::alreadyExistsAction` | Removed | ### ibexa/design-engine | Old FQN | New FQN / Comment | | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | | `\Ibexa\DesignEngine\Templating\TemplateNameResolverInterface::EZ_DESIGN_NAMESPACE` | Removed. Use the `\Ibexa\Contracts\DesignEngine\DesignAwareInterface::DESIGN_NAMESPACE` constant. | ### ibexa/elasticsearch Support for facets in `ibexa/elasticsearch` has been dropped, use the `Aggregation` API instead. | Old FQN | New FQN / Comment | | ------------------------------------------------------------------------------------------------------- | ----------------------------------- | | `\Ibexa\Contracts\Elasticsearch\Query\FacetBuilderVisitor` | Removed. Use the `Aggregation` API. | | `\Ibexa\Contracts\Elasticsearch\Query\FacetResultExtractor` | Removed. Use the `Aggregation` API. | | `\Ibexa\Elasticsearch\Query\FacetBuilderVisitor\AbstractTermsVisitor` | Removed. Use the `Aggregation` API. | | `\Ibexa\Elasticsearch\Query\FacetBuilderVisitor\ContentTypeVisitor` | Removed. Use the `Aggregation` API. | | `\Ibexa\Elasticsearch\Query\FacetBuilderVisitor\DispatcherVisitor` | Removed. Use the `Aggregation` API. | | `\Ibexa\Elasticsearch\Query\FacetBuilderVisitor\FilteredFacetVisitorDecorator` | Removed. Use the `Aggregation` API. | | `\Ibexa\Elasticsearch\Query\FacetBuilderVisitor\GlobalFacetVisitorDecorator` | Removed. Use the `Aggregation` API. | | `\Ibexa\Elasticsearch\Query\FacetBuilderVisitor\SectionVisitor` | Removed. Use the `Aggregation` API. | | `\Ibexa\Elasticsearch\Query\FacetBuilderVisitor\UserVisitor` | Removed. Use the `Aggregation` API. | | `\Ibexa\Elasticsearch\Query\ResultExtractor\FacetResultExtractor\AbstractTermsResultExtractor` | Removed. Use the `Aggregation` API. | | `\Ibexa\Elasticsearch\Query\ResultExtractor\FacetResultExtractor\ContentTypeResultExtractor` | Removed. Use the `Aggregation` API. | | `\Ibexa\Elasticsearch\Query\ResultExtractor\FacetResultExtractor\DispatcherResultExtractor` | Removed. Use the `Aggregation` API. | | `\Ibexa\Elasticsearch\Query\ResultExtractor\FacetResultExtractor\FilteredFacetResultExtractorDecorator` | Removed. Use the `Aggregation` API. | | `\Ibexa\Elasticsearch\Query\ResultExtractor\FacetResultExtractor\GlobalFacetResultExtractorDecorator` | Removed. Use the `Aggregation` API. | | `\Ibexa\Elasticsearch\Query\ResultExtractor\FacetResultExtractor\SectionResultExtractor` | Removed. Use the `Aggregation` API. | | `\Ibexa\Elasticsearch\Query\ResultExtractor\FacetResultExtractor\UserResultExtractor` | Removed. Use the `Aggregation` API. | ### ibexa/fieldtype-page | Old FQN | New FQN / Comment | | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | `\Ibexa\Bundle\FieldTypePage\DependencyInjection\Compiler\AbstractConfigurationAwareCompilerPass::EXTENSION_CONFIG_KEY` | Removed. Use the `\Ibexa\Bundle\FieldTypePage\DependencyInjection\IbexaFieldTypePageExtension::EXTENSION_NAME` constant. | | `\Ibexa\Bundle\FieldTypePage\DependencyInjection\Compiler\BlockDefinitionConfigurationCompilerPass::EXTENSION_CONFIG_KEY` | Removed. Use the `\Ibexa\Bundle\FieldTypePage\DependencyInjection\IbexaFieldTypePageExtension::EXTENSION_NAME` constant. | | `\Ibexa\FieldTypePage\FieldType\Page\Block\Renderer\Event\Listener\PreviewTemplateEventSubscriber` | Removed | | `\Ibexa\FieldTypePage\ScheduleBlock\ScheduleService::distributeItems` | Removed | ### ibexa/fieldtype-query | Old FQN | New FQN / Comment | | ---------------------------------------------------------------------------------------------- | ------------------------------------- | | `\Ibexa\FieldTypeQuery\QueryFieldPaginationService` | Removed | | `\Ibexa\FieldTypeQuery\Persistence\Legacy\Content\FieldValue\Converter\QueryConverter::create` | Removed. Use the default constructor. | ### ibexa/fieldtype-richtext | Old FQN | New FQN / Comment | | ---------------------------------------------------------------------------------------------- | ------------------------------------- | | `\Ibexa\FieldTypeRichText\Translation\Extractor\OnlineEditorCustomAttributesExtractor` | Removed | | `\Ibexa\FieldTypeQuery\Persistence\Legacy\Content\FieldValue\Converter\QueryConverter::create` | Removed. Use the default constructor. | > **Note: Missing custom tag configuration error** > > If the stored RichText record includes any custom tags that aren’t configured or recognized, saving the content will cause a validation error. ### ibexa/form-builder | Old FQN | New FQN / Comment | | ------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- | | `\Ibexa\Bundle\FormBuilder\DependencyInjection\Configuration::TREE_ROOT` | Removed. Use the `\Ibexa\Bundle\FormBuilder\DependencyInjection\IbexaFormBuilderExtension::EXTENSION_NAME` constant. | | `\Ibexa\FormBuilder\FieldType\Storage\FormStorage::getIndexData` | Removed | ### ibexa/graphql | Old FQN | New FQN / Comment | | ---------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | | `\Ibexa\GraphQL\Schema\ImagesVariationsBuilder` | Removed | | `\Ibexa\GraphQL\Schema\Domain\Content\NameHelper::domainContentCollectionField` | `\Ibexa\GraphQL\Schema\Domain\Content\NameHelper::itemConnectionField` | | `\Ibexa\GraphQL\Schema\Domain\Content\NameHelper::domainContentName` | `\Ibexa\GraphQL\Schema\Domain\Content\NameHelper::itemName` | | `\Ibexa\GraphQL\Schema\Domain\Content\NameHelper::domainContentConnection` | `\Ibexa\GraphQL\Schema\Domain\Content\NameHelper::itemConnectionName` | | `\Ibexa\GraphQL\Schema\Domain\Content\NameHelper::domainContentCreateInputName` | `\Ibexa\GraphQL\Schema\Domain\Content\NameHelper::itemCreateInputName` | | `\Ibexa\GraphQL\Schema\Domain\Content\NameHelper::domainContentUpdateInputName` | `\Ibexa\GraphQL\Schema\Domain\Content\NameHelper::itemUpdateInputName` | | `\Ibexa\GraphQL\Schema\Domain\Content\NameHelper::domainContentTypeName` | `\Ibexa\GraphQL\Schema\Domain\Content\NameHelper::itemUpdateInputName` | | `\Ibexa\GraphQL\Schema\Domain\Content\NameHelper::domainContentField` | `\Ibexa\GraphQL\Schema\Domain\Content\NameHelper::itemField` | | `\Ibexa\GraphQL\Schema\Domain\Content\NameHelper::domainMutationCreateContentField` | `\Ibexa\GraphQL\Schema\Domain\Content\NameHelper::itemMutationCreateItemField` | | `\Ibexa\GraphQL\Schema\Domain\Content\NameHelper::domainMutationUpdateContentField` | `\Ibexa\GraphQL\Schema\Domain\Content\NameHelper::itemMutationUpdateItemField` | | `\Ibexa\GraphQL\Schema\Domain\Content\NameHelper::domainGroupName` | `\Ibexa\GraphQL\Schema\Domain\Content\NameHelper::itemGroupName` | | `\Ibexa\GraphQL\Schema\Domain\Content\NameHelper::domainGroupTypesName` | `\Ibexa\GraphQL\Schema\Domain\Content\NameHelper::itemGroupTypesName` | | `\Ibexa\GraphQL\Schema\Domain\Content\Mapper\FieldDefinition\FieldDefinitionArgsBuilderMapper` | `\Ibexa\Contracts\GraphQL\Schema\Domain\Content\Mapper\FieldDefinition\FieldDefinitionMapper` | | `\Ibexa\GraphQL\Schema\Domain\Content\Mapper\FieldDefinition\FieldDefinitionInputMapper` | `\Ibexa\Contracts\GraphQL\Schema\Domain\Content\Mapper\FieldDefinition\FieldDefinitionMapper` | ### ibexa/measurement > **Note: Droppedmeasurementproduct attribute** > > The deprecated product attribute `measurement` has been removed. The change does not affect the [measurement field type](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/measurementfield/index.md). ### ibexa/migrations | Old FQN | New FQN / Comment | | --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | | `\Ibexa\Migration\ValueObject\ContentType\Matcher::CONTENT_TYPE_IDENTIFIER` | Removed. Use the `\Ibexa\Migration\StepExecutor\ContentType\IdentifierFinder::CONTENT_TYPE_IDENTIFIER` constant. | ### ibexa/product-catalog | Old FQN | New FQN / Comment | | ------------------------------ | ---------------------------------------- | | `\Ibexa\ProductCatalog\Bridge` | Migrate data to a local product catalog. | ### ibexa/page-builder | Old FQN | New FQN / Comment | | ------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | | `\Ibexa\Bundle\PageBuilder\DependencyInjection\IbexaPageBuilderExtension::SESSION_KEY_SITEACCESS` | Removed | | `\Ibexa\PageBuilder\PageBuilder\PreviewLanguageCodeResolver` | Removed | | `\Ibexa\PageBuilder\Siteaccess\SiteaccessService::resolveSiteAccessForLocation` | `\Ibexa\AdminUi\Siteaccess\SiteaccessResolverInterface::getSiteAccessesListForLocation` | | `\Ibexa\PageBuilder\Siteaccess\SiteaccessService::resolveSiteAccessForContent` | `\Ibexa\Contracts\PageBuilder\Siteaccess\SiteAccessResolver::resolveSiteAccessForContent` | | `\Ibexa\PageBuilder\Siteaccess\SiteaccessService::resolveSiteAccessBasedOnLanguage` | Removed | ### ibexa/rest | Old FQN | New FQN / Comment | | ----------------------------------------------------------------------- | --------------------------------------------------------------------------------- | | `\Ibexa\Bundle\Rest\EventListener\CsrfListener::isLoginRequest` | Add `csrf_protection: false` attribute to route definition. | | `\Ibexa\Bundle\Rest\EventListener\CsrfListener::isSessionRoute` | Add `csrf_protection: false` attribute to route definition. | | `\Ibexa\Bundle\Rest\EventListener\RequestListener::REST_PREFIX_PATTERN` | Use `\Ibexa\Contracts\Rest\UriParser\UriParserInterface::isRestRequest` function. | | `\Ibexa\Bundle\Rest\EventListener\RequestListener::hasRestPrefix` | Use `\Ibexa\Contracts\Rest\UriParser\UriParserInterface::isRestRequest` function. | | `\Ibexa\Bundle\Rest\RequestParser\Router` | `\Ibexa\Contracts\Rest\UriParser\UriParserInterface` | | `\Ibexa\Contracts\Rest\Output\Generator::generateMediaType` | `\Ibexa\Contracts\Rest\Output\Generator::generateMediaTypeWithVendor` | | `\Ibexa\Rest\Output\FieldTypeSerializer::serializeFieldValue` | `\Ibexa\Rest\Output\FieldTypeSerializer::serializeContentFieldValue` | | `\Ibexa\Rest\Server\Controller\Content::createView` | Forwards the request to the new `/views` location, but returns a 301. | | `\Ibexa\Rest\Server\Controller\User::$csrfTokenStorage` | Removed | | `\Ibexa\Rest\Server\Controller\User::$sessionController` | Removed | | `\Ibexa\Rest\Server\Controller\User::createSession` | `\Ibexa\Rest\Server\Controller\SessionController::refreshSessionAction` | | `\Ibexa\Rest\Server\Controller\User::refreshSession` | `\Ibexa\Rest\Server\Controller\SessionController::refreshSessionAction` | | `\Ibexa\Rest\Server\Controller\User::deleteSession` | `\Ibexa\Rest\Server\Controller\SessionController::refreshSessionAction` | To create a [JWT token](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_authentication/#jwt-authentication), XML isn't supported anymore. ### ibexa/scheduler | Old FQN | New FQN / Comment | | --------------------------------------------------------------- | ----------------------------------------------------------- | | `\Ibexa\Bundle\Rest\EventListener\CsrfListener::isLoginRequest` | Add `csrf_protection: false` attribute to route definition. | ### ibexa/site-factory | Old FQN | New FQN / Comment | | ------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- | | `\Ibexa\Bundle\SiteFactory\DependencyInjection\Configuration::TREE_ROOT` | Removed. Use the `\Ibexa\Bundle\SiteFactory\DependencyInjection\IbexaSiteFactoryExtension::EXTENSION_NAME` constant. | | `\Ibexa\SiteFactory\Event\EventDispatcher` | Removed | | `\Ibexa\SiteFactory\ServiceDecorator\SiteServiceDecorator` | Removed | | `\Ibexa\SiteFactory\ServiceEvent\Events\BeforeCreateSiteEvent` | Removed | | `\Ibexa\SiteFactory\ServiceEvent\Events\BeforeDeleteSiteEvent` | Removed | | `\Ibexa\SiteFactory\ServiceEvent\Events\BeforeUpdateSiteEvent` | Removed | | `\Ibexa\SiteFactory\ServiceEvent\Events\CreateSiteEvent` | Removed | | `\Ibexa\SiteFactory\ServiceEvent\Events\DeleteSiteEvent` | Removed | | `\Ibexa\SiteFactory\ServiceEvent\Events\UpdateSiteEvent` | Removed | ### ibexa/solr Support for facet search has been dropped, use the `Aggregation` API instead. | Old FQN | New FQN / Comment | | ---------------------------------------------------------- | ------------------------------------------------------------ | | `\Ibexa\Solr\Handler::$resultExtractor` | Use `$contentResultExtractor` or `$locationResultExtractor`. | | `\Ibexa\Solr\Gateway\UpdateSerializer` | `\Ibexa\Solr\Gateway\UpdateSerializer\XmlUpdateSerializer` | | `\Ibexa\Solr\Query\FacetBuilderVisitor` | Use `Aggregation API`. | | `\Ibexa\Solr\Query\FacetFieldVisitor` | Use `Aggregation API`. | | `\Ibexa\Solr\Query\Common\FacetBuilderVisitor\Aggregate` | Use `Aggregation API`. | | `\Ibexa\Solr\Query\Common\FacetBuilderVisitor\ContentType` | Use `Aggregation API`. | | `\Ibexa\Solr\Query\Common\FacetBuilderVisitor\Section` | Use `Aggregation API`. | | `\Ibexa\Solr\Query\Common\FacetBuilderVisitor\User` | Use `Aggregation API`. | | `\Ibexa\Solr\Query\Content\CriterionVisitor\Field` | `\Ibexa\Solr\Query\Common\CriterionVisitor\Field` | ### ibexa/storefront | Old FQN | New FQN / Comment | | ------------------------------------------------------------------------------ | --------------------------------------------------------------------- | | `\Ibexa\Contracts\Storefront\Repository\TaxonomyTreeServiceInterface::getPath` | `\Ibexa\Contracts\Taxonomy\Service\TaxonomyServiceInterface::getPath` | ### ibexa/system-info | Old FQN | New FQN / Comment | | --------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | | `\Ibexa\Bundle\SystemInfo\SystemInfo\Collector\IbexaSystemInfoCollector::CONTENT_PACKAGES` | Removed. Use the `\Ibexa\Bundle\SystemInfo\SystemInfo\Collector\IbexaSystemInfoCollector::HEADLESS_PACKAGES` constant. | | `\Ibexa\Bundle\SystemInfo\SystemInfo\Collector\IbexaSystemInfoCollector::ENTERPRISE_PACKAGES` | Removed. Use `IbexaSystemInfoCollector::EXPERIENCE_PACKAGES` or `IbexaSystemInfoCollector::HEADLESS_PACKAGES` constant. | | `\Ibexa\Bundle\SystemInfo\SystemInfo\Value\IbexaSystemInfo::$stability` | `\Ibexa\Bundle\SystemInfo\SystemInfo\Value\IbexaSystemInfo` is considered internal. | ### ibexa/workflow | Old FQN | New FQN / Comment | | -------------------------------------------------------------------------------------------------- | ----------------- | | `\Ibexa\Contracts\Workflow\Service\WorkflowServiceInterface::loadWorkflowMetadataOriginatedByUser` | Removed | | `\Ibexa\Contracts\Workflow\Service\WorkflowServiceInterface::loadAllWorkflowMetadata` | Removed | ## PHP method parameters The `ValueObject` argument was replaced by `object` in a number of interfaces in `core` and `migrations` package. In `core`, this change improves extensibility by enabling the use of custom object types to be interpreted by `PermissionResolver`. In `migrations`, it makes it easier to integrate custom data types, especially when using `AbstractStepFactory`. > **Note: Change examples** > > Below the lists you may find examples of changes in those interfaces or classes that you are most likely to use in your work. ### ibexa/core | PHP Interface or class | Methods | | ---------------------------------------------------------- | ------------------------------ | | `Ibexa\Contracts\Core\Repository\PermissionResolver` | `canUser`, `lookupLimitations` | | `Ibexa\Contracts\Core\Limitation/TargetAwareType` | `evaluate` | | `Ibexa\Contracts\Core\Limitation/Type` | `evaluate` | | `Ibexa\Core\Limitation\BlockingLimitationType` | `evaluate` | | `Ibexa\Core\Limitation\ChangeOwnerLimitationType` | `evaluate` | | `Ibexa\Core\Limitation\ContentTypeLimitationType` | `evaluate` | | `Ibexa\Core\Limitation\LanguageLimitationType` | `evaluate` | | `Ibexa\Core\Limitation\LocationLimitationType` | `evaluate` | | `Ibexa\Core\Limitation\MemberOfLimitationType` | `evaluate` | | `Ibexa\Core\Limitation\NewObjectStateLimitationType` | `evaluate` | | `Ibexa\Core\Limitation\NewSectionLimitationType` | `evaluate` | | `Ibexa\Core\Limitation\ObjectStateLimitationType` | `evaluate` | | `Ibexa\Core\Limitation\OwnerLimitationType` | `evaluate` | | `Ibexa\Core\Limitation\ParentContentTypeLimitationType` | `evaluate` | | `Ibexa\Core\Limitation\ParentDepthLimitationType` | `evaluate` | | `Ibexa\Core\Limitation\ParentOwnerLimitationType` | `evaluate` | | `Ibexa\Core\Limitation\ParentUserGroupLimitationType` | `evaluate` | | `Ibexa\Core\Limitation\RoleLimitationType` | `evaluate` | | `Ibexa\Core\Limitation\SectionLimitationType` | `evaluate` | | `Ibexa\Core\Limitation\SiteAccessLimitationType` | `evaluate` | | `Ibexa\Core\Limitation\StatusLimitationType` | `evaluate` | | `Ibexa\Core\Limitation\SubtreeLimitationType` | `evaluate` | | `Ibexa\Core\Limitation\UserGroupLimitationType` | `evaluate` | | `Ibexa\Core\Repository\Permission\CachedPermissionService` | `canUser`, `lookupLimitations` | | `Ibexa\Core\Repository\Permission\PermissionResolver` | `canUser`, `lookupLimitations` | Changes in `src/contracts/Repository/PermissionResolver.php` *[Image: PermissionResolver.php]* ### ibexa/migrations | PHP Interface or class | Methods | | -------------------------------------------------------------------------- | -------------------------------------- | | `Ibexa\Contracts\Migration\StepExecutor\AbstractStepExecutor` | `doCollectReferences`, `handleActions` | | `Ibexa\Migration\Generator\Content\StepBuilder\Create` | `build` | | `Ibexa\Migration\Generator\Content\StepBuilder\Delete` | `build` | | `Ibexa\Migration\Generator\Content\StepBuilder\Factory` | `prepareLogMessage` | | `Ibexa\Migration\Generator\Content\StepBuilder\Update` | `build` | | `Ibexa\Migration\Generator\ContentTypeGroup\StepBuilder\Create` | `build` | | `Ibexa\Migration\Generator\ContentTypeGroup\StepBuilder\Delete` | `build` | | `Ibexa\Migration\Generator\ContentTypeGroup\StepBuilder\Factory` | `prepareLogMessage` | | `Ibexa\Migration\Generator\ContentTypeGroup\StepBuilder\Update` | `build` | | `Ibexa\Migration\Generator\Language\StepBuilder\Create` | `build` | | `Ibexa\Migration\Generator\Language\StepBuilder\Factory` | `prepareLogMessage` | | `Ibexa\Migration\Generator\Location\StepBuilder\Factory` | `prepareLogMessage` | | `Ibexa\Migration\Generator\Location\StepBuilder\Update` | `build` | | `Ibexa\Migration\Generator\ObjectState\StepBuilder\Create` | `build` | | `Ibexa\Migration\Generator\ObjectState\StepBuilder\Factory` | `prepareLogMessage` | | `Ibexa\Migration\Generator\ObjectStateGroup\StepBuilder\Create` | `build` | | `Ibexa\Migration\Generator\ObjectStateGroup\StepBuilder\Factory` | `prepareLogMessage` | | `Ibexa\Migration\Generator\Role\StepBuilder\RoleCreateStepBuilder` | `build` | | `Ibexa\Migration\Generator\Role\StepBuilder\RoleDeleteStepBuilder` | `build` | | `Ibexa\Migration\Generator\Role\StepBuilder\RoleStepFactory` | `prepareLogMessage` | | `Ibexa\Migration\Generator\Role\StepBuilder\RoleUpdateStepBuilder` | `build` | | `Ibexa\Migration\Generator\Section\StepBuilder\Create` | `build` | | `Ibexa\Migration\Generator\Section\StepBuilder\Factory` | `prepareLogMessage` | | `Ibexa\Migration\Generator\Section\StepBuilder\Update` | `build` | | `Ibexa\Migration\Generator\StepBuilder\AbstractStepFactory` | `create`, `log`, `prepareLogMessage` | | `Ibexa\Migration\Generator\StepBuilder\ContentTypeCreateStepBuilder` | `build` | | `Ibexa\Migration\Generator\StepBuilder\ContentTypeDeleteStepBuilder` | `build` | | `Ibexa\Migration\Generator\StepBuilder\ContentTypeStepFactory` | `prepareLogMessage` | | `Ibexa\Migration\Generator\StepBuilder\ContentTypeUpdateStepBuilder` | `build` | | `Ibexa\Migration\Generator\StepBuilder\LoggerContentTypeCreateStepBuilder` | `build` | | `Ibexa\Migration\Generator\StepBuilder\StepBuilderInterface` | `build` | | `Ibexa\Migration\Generator\StepBuilder\StepFactoryInterface` | `build` | | `Ibexa\Migration\Generator\User\StepBuilder\Create` | `build` | | `Ibexa\Migration\Generator\User\StepBuilder\Factory` | `prepareLogMessage` | | `Ibexa\Migration\Generator\User\StepBuilder\Update` | `build` | | `Ibexa\Migration\Generator\UserGroup\StepBuilder\Create` | `build` | | `Ibexa\Migration\Generator\UserGroup\StepBuilder\Delete` | `build` | | `Ibexa\Migration\Generator\UserGroup\StepBuilder\Factory` | `prepareLogMessage` | | `Ibexa\Migration\Generator\UserGroup\StepBuilder\Update` | `build` | | `Ibexa\Migration\StepExecutor\ReferenceDefinition\Resolver` | `resolve` | | `Ibexa\Migration\StepExecutor\ReferenceDefinition\ResolverInterface` | `resolve` | Changes in `Ibexa\Migration\Generator\StepBuilder\StepFactoryInterface` *[Image: StepFactoryInterface.php]* Changes in `Ibexa\Migration\StepExecutor\ReferenceDefinition\ResolverInterface` *[Image: ResolverInterface.php]* Changes in `Ibexa\Migration\Generator\StepBuilder\AbstractStepFactory` *[Image: AbstractStepFactory.php]* ## Services The following service definitions have been removed: | Service name | Comment | | ---------------------------------------------- | ------- | | `ibexa.cart.number_formatter.currency.factory` | Removed | ## JavaScript classes and functions | Old class or function | New class or function | | ----------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | | `formatLine` const in `/src/bundle/Resources/public/js/scripts/helpers/form.error.helper.js` | Removed | | `parseAll` const in `/src/bundle/Resources/public/js/scripts/helpers/middle.ellipsis.js` | Removed | | `fileSizeToString` const in `src/bundle/ui-dev/src/modules/multi-file-upload/helpers/text.helper.js` | Use `fileSizeToString` function from `/src/bundle/ui-dev/src/modules/common/helpers/text.helper.js`. | | `src/bundle/ui-dev/src/modules/common/components/backdrop/backdrop.js` | Use the `ibexa.core.Backdrop` component. | | `src/bundle/ui-dev/src/modules/page-builder/components/block/sidebar.block.js` | `src/bundle/ui-dev/src/modules/page-builder/components/block/block.js` | | `src/bundle/ui-dev/src/modules/page-builder/components/block/sidebar.blocks.group.js` | `src/bundle/ui-dev/src/modules/page-builder/components/block/blocks.group.js` | | `src/bundle/ui-dev/src/modules/page-builder/components/sidebar/sidebar.js` | `src/bundle/ui-dev/src/modules/page-builder/components/toolbox.js` | | `src/bundle/ui-dev/src/modules/tree-builder/components/indentation-vertical/indentation.vertical.js)` | `src/bundle/ui-dev/src/modules/tree-builder/components/indentation-horizontal/indentation.horizontal.js` | | `src/bundle/ui-dev/src/modules/tree-builder/components/portal-provider/portal.provider.js` | `tree-builder/src/bundle/ui-dev/src/modules/tree-builder/components/portal/portal.js` | | `src/bundle/ui-dev/src/modules/tree-builder/hooks/usePortal.js` | `tree-builder/src/bundle/ui-dev/src/modules/tree-builder/components/portal/portal.js` | ## Configuration keys | Old name | New name | | ------------------------------------------------------------- | -------------------------------------------------------------------- | | `ibexa.system.*.database.*` | `ibexa.repositories` | | `ibexa.system.*.pagelayout` | `ibexa.system.*.page_layout` | | `ibexa.system.*.session_name` | `ibexa.system.*.session.name` | | `ibexa.site_access.config.default.user_registration.group_id` | `ibexa.site_access.config.default.user_registration.group_remote_id` | | `ezpublish_http_basic` | Use `http_basic` in `security.yml` directly. | ## Session prefix The default prefix used for [SiteAccess sessions](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/sessions/index.md) has been renamed. | Old prefix | New prefix | | ---------- | ---------------- | | `eZSESSID` | `IBX_SESSION_ID` | ## CSS settings | Old setting | New setting | | --------------------------------------------------------- | -------------------------- | | `ibexa-alert--complementary` | `ibexa-alert--info` | | `sidebar-drag-items` | `toolbox-drag-items` | | `sidebar-drag-items-group` | `toolbox-drag-items-group` | | `sidebar-drag-item` | `tooblox-drag-item` | | `/src/bundle/Resources/public/scss/mixins/_font.scss` | Removed | | `/src/bundle/Resources/public/scss/_iframe-backdrop.scss` | Removed | ## Twig templates, functions and filters The global Twig variable `ez_richtext_config` has been renamed to `ibexa_richtext_config`. | Old name | New name / Comment | | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | | `\Ibexa\Core\MVC\Symfony\Templating\Twig\Extension\` | Removed `ezplatform` variable, use the `ibexa` global variable. | | `\Ibexa\Core\MVC\Symfony\View\ParametersInjector\ViewbaseLayout\` | Removed `pagelayout` variable, use `page_layout`. | | `/src/bundle/Resources/views/themes/admin/account/form_fields.html.twig` | Deprecated, extend `@ibexadesign/ui/form_fields.html.twig` directly. | | `/src/bundle/Resources/views/themes/admin/content/edit/content_header.html.twig` | Removed | | `/src/bundle/Resources/views/themes/admin/ui/footer.html.twig` | Deprecated | | `/src/bundle/Resources/views/themes/corporate/customer_portal/registration/registration_already_exists.html.twig` | Removed | | `/src/bundle/Resources/views/block_preview.html.twig` | Removed | | `\Ibexa\Scheduler\Dashboard\AllScheduledTab` | Removed `type` variable. Use `content_type.name`. | | `\Ibexa\Scheduler\Dashboard\MyScheduledTab` | Removed `type` variable. Use `content_type.name`. | | `\Ibexa\Bundle\User\Controller\DefaultProfileImageController` | Removed `type` variable. Use `text_color`. Remove `default(text)` from `initials.svg.twig`. | | `\Ibexa\Bundle\User\Controller\DefaultProfileImageController` | Removed `background` variable, use `background_color`. Remove `default(background)` from `initials.svg.twig`. | # Ibexa DXP v4.6 LTS ## Integrated help v4.6.29 (Headless, Experience, Commerce, LTS Update, New feature) 2026-04-20 ### Product tour The product tour is a new Integrated help feature that helps back office contributors to discover Ibexa DXP. With product tours, you can create customized onboarding journeys. This accelerates user adoption, reduces training time, and helps users confidently navigate the platform. For more information, see [Product tour](https://doc.ibexa.co/en/4.6/administration/back_office/product_tour/). ## Ibexa DXP v4.6.29 (Headless, Experience, Commerce, New feature) 2026-04-20 ### Developer experience #### Taxonomy search One [taxonomy search](https://doc.ibexa.co/en/4.6/content_management/taxonomy/taxonomy_api/search) criterion is added: - [`TaxonomyNoEntries`](https://doc.ibexa.co/en/4.6/search/criteria_reference/taxonomy_no_entries/) to find content items to which no taxonomy entries have been assigned. #### Custom parameters in `ibexa_render()` You can now pass custom parameters to templates when using the `ibexa_render()` Twig function with the new `params` option, similar to how you can with `render(controller())`. This allows you to provide additional context or data to your view templates: ``` {{ ibexa_render(content, { 'viewType': 'line', 'method': 'inline', 'params': { 'custom_param': 'custom_value', 'another_param': 'another_value' } }) }} ``` The parameters are available in your template as regular variables. For more information, see [`ibexa_render()` Twig function](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/content_twig_functions/#ibexa_render). #### PHP API The following additions were made to the PHP API: - [`Ibexa\Contracts\Core\FieldType\ReferenceAwareExternalStorage`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-FieldType-ReferenceAwareExternalStorage.html) - [`Ibexa\Contracts\Core\Options\Context`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Options-Context.html) - [`Ibexa\Contracts\CorporateAccount\Order`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/namespaces/ibexa-contracts-corporateaccount-order.html) - [`Ibexa\Contracts\CorporateAccount\Order\OrderStatusLabelProviderInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-CorporateAccount-Order-OrderStatusLabelProviderInterface.html) - [`Ibexa\Contracts\Taxonomy\Search\Query\Criterion\TaxonomyNoEntries`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Taxonomy-Search-Query-Criterion-TaxonomyNoEntries.html) For more information, see [search criteria reference entry](https://doc.ibexa.co/en/4.6/search/criteria_reference/taxonomy_no_entries/). - [`Ibexa\Contracts\IntegratedHelp` namespace](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/namespaces/ibexa-contracts-integratedhelp.html) from the [Integrated help LTS-Update](https://doc.ibexa.co/en/4.6/administration/back_office/integrated_help/) ### Full changelog To learn more about all the included changes, see the full release change logs: - [Ibexa Headless v4.6.29](https://github.com/ibexa/headless/releases/tag/v4.6.29) - [Ibexa Experience v4.6.29](https://github.com/ibexa/experience/releases/tag/v4.6.29) - [Ibexa Commerce v4.6.29](https://github.com/ibexa/commerce/releases/tag/v4.6.29) To update your application, see the [update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.6/update_from_4.6/#v4629). ## Ibexa DXP v4.6.28 (Headless, Experience, Commerce) 2026-03-05 ### Infrastructure #### PHP 8.4 support PHP 8.4 is now [officially supported](https://doc.ibexa.co/en/4.6/getting_started/requirements/#php). ### Developer experience #### PHP API The following event have been added to the PHP API: - [`Ibexa\Contracts\ImageEditor\Event\ConfigureImageOptimizersEvent`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ImageEditor-Event-ConfigureImageOptimizersEvent.html) ### Full changelog To learn more about all the included changes, see the full release change logs: - [Ibexa Headless v4.6.28](https://github.com/ibexa/headless/releases/tag/v4.6.28) - [Ibexa Experience v4.6.28](https://github.com/ibexa/experience/releases/tag/v4.6.28) - [Ibexa Commerce v4.6.28](https://github.com/ibexa/commerce/releases/tag/v4.6.28) To update your application, see the [update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.6/update_from_4.6/#v4628). ## Ibexa DXP v4.6.27 (Headless, Experience, Commerce) 2026-02-03 ### Added support for Elasticsearch 8 Elasticsearch 8 is now officially supported. If you're currently using Elasticsearch 7, which is [no longer maintained](https://www.elastic.co/support/eol), it's recommended to upgrade. See the [update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.6/update_from_4.6/#elasticsearch-8-support) for more information. ### Added asynchronous processing of data in Ibexa DXP You can now process requests from [Ibexa CDP](https://doc.ibexa.co/en/4.6/cdp/cdp/) asynchronously, in the background. Use it to improve performance and prevent data loss. To enable this behavior, install and configure the [Ibexa Messenger package](https://doc.ibexa.co/en/4.6/infrastructure_and_maintenance/background_tasks/). Then, set the batch size that triggers asynchronous processing: ``` ibexa_cdp: bulk_async_threshold: 100 ``` When the number of [audience](https://content.raptorservices.com/help-center/how-to-build-audiences-in-the-customer-data-platform) changes coming from [Raptor](https://www.raptorservices.com/) exceeds this number, the changes are sent to the queue and processed in the background. Otherwise, they are processed synchronously. ### Improved HTTP caching for Page Builder and dashboard blocks (Experience) (Commerce) You can now indicate which [query parameters](https://en.wikipedia.org/wiki/Query_string) must be used as keys when generating [HTTP cache](https://doc.ibexa.co/en/4.6/infrastructure_and_maintenance/cache/http_cache/http_cache/) for block requests. This allows you to improve performance for blocks by utilizing HTTP cache more effectively, for example, for paginated blocks in the [dashboard](https://doc.ibexa.co/en/4.6/administration/dashboard/customize_dashboard/). To set it up, use the new `cacheable_query_params` [block setting](https://doc.ibexa.co/en/4.6/content_management/pages/page_blocks/#block-configuration). Then, adjust your [layouts](https://doc.ibexa.co/en/4.6/templating/render_content/render_page/#configure-layout) and pass the parameters to [Symfony's `controller function`](https://symfony.com/doc/7.4/reference/twig_reference.html#controller) by using the new `ibexa_append_cacheable_query_params` Twig function, as in the example below: ``` {{ render_esi(controller('Ibexa\\Bundle\\FieldTypePage\\Controller\\BlockController::renderAction', { 'locationId': locationId, 'contentId': contentInfo.id, 'blockId': block.id, 'versionNo': versionInfo.versionNo, 'languageCode': field.languageCode }, ibexa_append_cacheable_query_params(block) )) }} ``` ### Developer experience #### Easier debugging of Page Builder blocks In Symfony's `dev` environment, use the "Open profiler" action to quickly debug Page Builder's block rendering failures. *[Image: Quickly debug failing Page Builder blocks with "Open profiler" action]* #### Improved logging for Ibexa CDP You can configure the new `ibexa.cdp.webhook` Monolog channels to direct all CDP webhook logs to specific output for easier separation of logs. Example configuration: ``` when@prod: monolog: handlers: cdp_webhook: type: stream path: "%kernel.logs_dir%/cdp_webhook_%kernel.environment%.log" level: debug channels: [ 'ibexa.cdp.webhook' ] ``` #### Simplified creation of product types Use the new [`ProductTypeCreateStruct::setNames()`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Local-Values-ProductType-ProductTypeCreateStruct.html#method_setNames) method to set names, in multiple languages, of a product type during its creation. See [creating product types](https://doc.ibexa.co/en/4.6/pim/product_api/#creating-product-types) for an example. #### PHP API The PHP API has been enhanced with the following classes and interfaces: - [`Ibexa\Contracts\Core\Search\Embedding\EmbeddingProviderExceptionInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Search-Embedding-EmbeddingProviderExceptionInterface.html) - [`Ibexa\Contracts\Taxonomy\Embedding\Exception\TaxonomyEmbeddingConfigurationException`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Taxonomy-Embedding-Exception-TaxonomyEmbeddingConfigurationException.html) ### Full changelog To learn more about all the included changes, see the full release change logs: - [Ibexa Headless v4.6.27](https://github.com/ibexa/headless/releases/tag/v4.6.27) - [Ibexa Experience v4.6.27](https://github.com/ibexa/experience/releases/tag/v4.6.27) - [Ibexa Commerce v4.6.27](https://github.com/ibexa/commerce/releases/tag/v4.6.27) To update your application, see the [update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.6/update_from_4.6/#v4627). ## Integrated help v4.6.26 (Headless, Experience, Commerce, LTS Update, New feature, First release) 2025-12-10 Integrated help, a new [LTS Update](https://doc.ibexa.co/en/4.6/ibexa_products/editions/#lts-updates), brings contextual documentation, guidance, and partner-specific resources right into the user interface of Ibexa DXP. It helps editors, store managers, and developers to quickly access relevant content, training and resources without leaving the UI, narrowing the gap between product and documentation. The default help menu can be modified to include links to internal editorial guidelines, custom tutorials, or support pages. *[Image: Integrated help menu]* For more information, see [Integrated help](https://doc.ibexa.co/en/4.6/administration/back_office/integrated_help/). ## Collaboration v4.6.26 (Headless, Experience, Commerce, LTS Update, New feature) 2025-12-10 #### Real-time collaborative editing Real-time editing is now part of the [Collaborative editing](https://doc.ibexa.co/en/4.6/content_management/collaborative_editing/collaborative_editing/) feature. By using it, users can edit and review content in real time, making teamwork faster, more efficient, and streamlining the content review process. The system automatically tracks changes, allowing seamless collaboration within a single content item. This extends the already existing capabilities allowing editors to work on the same content created in Ibexa DXP simultaneously, streamlining the content creation and review process. *[Image: Participants list]* For more information, see how to [install Collaborative editing](https://doc.ibexa.co/en/4.6/content_management/collaborative_editing/install_collaborative_editing). #### PHP API The PHP API has been enhanced with the following classes and interfaces: - [`Ibexa\Contracts\Collaboration\Invitation\Query\Criterion\ParticipantScope`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Invitation-Query-Criterion-ParticipantScope.html) - [`Ibexa\Contracts\Collaboration\Invitation\Query\Criterion\ParticipantType`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Invitation-Query-Criterion-ParticipantType.html) - [`Ibexa\Contracts\Collaboration\Participant\ParticipantDiscriminator`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Collaboration-Participant-ParticipantDiscriminator.html) - [`Ibexa\Contracts\FieldTypeRichTextRTE\ChannelIdGeneratorInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-FieldTypeRichTextRTE-ChannelIdGeneratorInterface.html) - [`Ibexa\Contracts\FieldTypeRichTextRTE\Config\LicenseKeyProviderInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-FieldTypeRichTextRTE-Config-LicenseKeyProviderInterface.html) - [`Ibexa\Contracts\FieldTypeRichTextRTE\Config\LocalStorageInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-FieldTypeRichTextRTE-Config-LocalStorageInterface.html) - [`Ibexa\Contracts\FieldTypeRichTextRTE\TokenServiceInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-FieldTypeRichTextRTE-TokenServiceInterface.html) - [`Ibexa\Contracts\FieldTypeRichTextRTE\ToS\LicenseTermsStatusServiceInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-FieldTypeRichTextRTE-ToS-LicenseTermsStatusServiceInterface.html) - [`Ibexa\Contracts\FieldTypeRichTextRTE\ToS\NoResponseException`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-FieldTypeRichTextRTE-ToS-NoResponseException.html) - [`Ibexa\Contracts\FieldTypeRichTextRTE\ToS\Status`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-FieldTypeRichTextRTE-ToS-Status.html) - [`Ibexa\Contracts\FieldTypeRichTextRTE\ToS\ToSServiceInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-FieldTypeRichTextRTE-ToS-ToSServiceInterface.html) - [`Ibexa\Contracts\Share\Mapper\Action\ShareActionItemsMapperInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Share-Mapper-Action-ShareActionItemsMapperInterface.html) ## AI Actions v4.6.26 (Headless, Experience, Commerce, LTS Update, New feature) 2025-12-10 #### Taxonomy suggestions for faster content classification You can now speed up taxonomy assignment with AI-powered taxonomy suggestions. Instead of manually browsing through large taxonomy trees and selecting categories or tags one by one, editors can choose from automatically generated suggestions based on the product or content information, for example name and description. This approach reduces manual effort, minimizes errors, and significantly improves the speed and consistency of content and product classification. *[Image: Taxonomy entries suggested by the AI Assistant]* For more information, see [Taxonomy suggestions](https://doc.ibexa.co/en/4.6/content_management/taxonomy/taxonomy/#taxonomy-suggestions). #### PHP API The PHP API has been enhanced with the following classes: - [`Ibexa\Contracts\ConnectorAi\Action\DataType\Taxonomy`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-DataType-Taxonomy.html) - [`Ibexa\Contracts\ConnectorAi\Action\DataType\TaxonomyEntry`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-DataType-TaxonomyEntry.html) - [`Ibexa\Contracts\ConnectorAi\Action\DataType\TaxonomySuggestion`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-DataType-TaxonomySuggestion.html) - [`Ibexa\Contracts\ConnectorAi\Action\DataType\TaxonomySuggestionInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-DataType-TaxonomySuggestionInterface.html) - [`Ibexa\Contracts\ConnectorAi\Action\DataType\TextToTaxonomyInput`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-DataType-TextToTaxonomyInput.html) - [`Ibexa\Contracts\ConnectorAi\Action\Response\TaxonomyResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-Response-TaxonomyResponse.html) - [`Ibexa\Contracts\ConnectorAi\Action\SuggestTaxonomyAction`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-SuggestTaxonomyAction.html) - [`Ibexa\Contracts\ConnectorAi\Action\TextToTaxonomy\Action`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-TextToTaxonomy-Action.html) - [`Ibexa\Contracts\ConnectorAi\Action\TextToTaxonomy\ActionResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-TextToTaxonomy-ActionResponse.html) - [`Ibexa\Contracts\ConnectorAi\Action\TextToTaxonomy\ActionType`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-TextToTaxonomy-ActionType.html) ## Ibexa DXP v4.6.26 (Headless, Experience, Commerce, New feature) 2025-12-10 #### Security This release includes security fixes. To learn more, see the [corresponding security advisory](https://developers.ibexa.co/security-advisories/ibexa-sa-2025-005-password-change-and-xss-vulnerabilities-in-back-office). #### Infrastructure - MariaDB 11.4 is now [officially supported](https://doc.ibexa.co/en/4.6/getting_started/requirements/#dbms) #### Developer experience ##### PHP API The PHP API has been enhanced with the following classes and interfaces: - [`Ibexa\Contracts\Core\Repository\Values\Content\EmbeddingQuery`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-EmbeddingQuery.html) - [`Ibexa\Contracts\Core\Repository\Values\Content\EmbeddingQueryBuilder`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-EmbeddingQueryBuilder.html) - [`Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContentTypeGroupName`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-ContentType-Query-Criterion-ContentTypeGroupName.html) - [`Ibexa\Contracts\Core\Repository\Values\Content\Query\Embedding`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Embedding.html) - [`Ibexa\Contracts\Core\Repository\Values\Content\QueryValidatorInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-QueryValidatorInterface.html) - [`Ibexa\Contracts\Core\Search\Embedding\EmbeddingConfigurationInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Search-Embedding-EmbeddingConfigurationInterface.html) - [`Ibexa\Contracts\Core\Search\Embedding\EmbeddingProviderInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Search-Embedding-EmbeddingProviderInterface.html) - [`Ibexa\Contracts\Core\Search\Embedding\EmbeddingProviderRegistryInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Search-Embedding-EmbeddingProviderRegistryInterface.html) - [`Ibexa\Contracts\Core\Search\Embedding\EmbeddingProviderResolverInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Search-Embedding-EmbeddingProviderResolverInterface.html) - [`Ibexa\Contracts\Core\Search\Embedding\EmbeddingResolverNotFoundException`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Search-Embedding-EmbeddingResolverNotFoundException.html) - [`Ibexa\Contracts\Core\Search\FieldType\EmbeddingField`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Search-FieldType-EmbeddingField.html) - [`Ibexa\Contracts\Core\Search\FieldType\EmbeddingFieldFactory`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Search-FieldType-EmbeddingFieldFactory.html) - [`Ibexa\Contracts\Elasticsearch\Query\EmbeddingVisitor`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Elasticsearch-Query-EmbeddingVisitor.html) - [`Ibexa\Contracts\AdminUi\ContentType\ContentTypeFieldsByExpressionServiceInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-AdminUi-ContentType-ContentTypeFieldsByExpressionServiceInterface.html) - [`Ibexa\Contracts\Solr\Query\EmbeddingVisitor`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Solr-Query-EmbeddingVisitor.html) - [`Ibexa\Contracts\Taxonomy\Embedding\TaxonomyEmbeddingConfigurationInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Taxonomy-Embedding-TaxonomyEmbeddingConfigurationInterface.html) - [`Ibexa\Contracts\Taxonomy\Embedding\TaxonomyEmbeddingFieldProviderInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Taxonomy-Embedding-TaxonomyEmbeddingFieldProviderInterface.html) - [`Ibexa\Contracts\Taxonomy\Search\Query\Value\TaxonomyEmbedding`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Taxonomy-Search-Query-Value-TaxonomyEmbedding.html) - [`Ibexa\Contracts\User\PasswordReset\NotifierInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-User-PasswordReset-NotifierInterface.html) ## Ibexa DXP v4.6.25 (Headless, Experience, Commerce) 2024-10-17 #### Security This release includes security fixes. To learn more, see the [corresponding security advisory](https://developers.ibexa.co/security-advisories/ibexa-sa-2025-004-xss-and-enumeration-vulnerabilities-in-back-office). To learn more about all the included changes, see the full release change logs: - [Ibexa Headless v4.6.25](https://github.com/ibexa/headless/releases/tag/v4.6.25) - [Ibexa Experience v4.6.25](https://github.com/ibexa/experience/releases/tag/v4.6.25) - [Ibexa Commerce v4.6.25](https://github.com/ibexa/commerce/releases/tag/v4.6.25) To update your application, see the [update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.6/update_from_4.6/#v4625). ## Collaboration v4.6.24 (Headless, Experience, Commerce, LTS Update, New feature, First release) 2025-09-09 #### Collaboration The new [Collaborative editing](https://doc.ibexa.co/en/4.6/content_management/collaborative_editing/collaborative_editing_guide/) LTS Update allows multiple users to preview, review, and edit the same content, improving teamwork and streamlining the review process. Internal and external users can be invited to a collaboration session, through different sharing options. With Real-time editing, more advanced part of the feature, users can see each other’s changes in the real time, or work on the content asynchronously. Additionally, shared drafts can be accessed and managed through new dashboard tabs: **My shared drafts** and **Drafts shared with me**, helping users stay organized. ## AI Actions v4.6.24 (Headless, Experience, Commerce, LTS Update) 2025-09-09 #### Chat GPT 5.0 support With improved reasoning and greater accuracy in mind, the AI Connector package has been enhanced by adding ChatGPT 5.0 to its list of supported LLMs. *[Image: ChatGPT 5.0 on a list of supported LLMs]* ## Discounts v4.6.24 (Commerce, LTS Update) 2025-09-09 #### Discount indexing Discounts now allow scheduling a re-indexing of discounted product catalog prices at the most convenient time by using the Ibexa Messenger package. Ibexa Messenger is a customization of the Symfony Messenger package, created to adjust it to Ibexa DXP's needs. Once properly configured, it uses a background queue to trigger price re-indexing, ensuring efficient use of system resources without causing performance disruptions. ##### PHP API The following additions were made to the Discounts PHP API: Events - [`Ibexa\Contracts\Discounts\Event\EnableDiscountEvent`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-EnableDiscountEvent.html) - [`Ibexa\Contracts\Discounts\Event\BeforeDisableDiscountEvent`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-BeforeDisableDiscountEvent.html) - [`Ibexa\Contracts\Discounts\Event\BeforeEnableDiscountEvent`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-BeforeEnableDiscountEvent.html) - [`Ibexa\Contracts\Discounts\Event\DisableDiscountEvent`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-DisableDiscountEvent.html) Search criteria - [`Ibexa\Contracts\Discounts\Value\Query\Criterion\IndexedAtCriterion`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-IndexedAtCriterion.html) - [`Ibexa\Contracts\Discounts\Value\Query\Criterion\UpdatedAtCriterion`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-UpdatedAtCriterion.html) ## Ibexa DXP v4.6.24 (Headless, Experience, Commerce, New feature) 2025-09-09 #### Improvements to notifications An improved notifications system is now more intuitive. Developers can now create and configure their own notification types, while users can now browse through a list of notifications, where they can either act on them or dismiss them. *[Image: A searchable notifications list]* #### Developer experience ##### New packages The only package that has been introduced in Ibexa DXP v4.6.24 is ibexa/messenger. ##### New version of PHP Storm Plugin To further improve your experience with Ibexa DXP, a 1.14.0 version of [PHP Storm Plugin](https://doc.ibexa.co/en/4.6/resources/phpstorm_plugin/) has been released, which brings the following changes: - Added support for Ibexa DXP v5.0 - Added compatibility with PhpStorm 2024.3.6+ - Added file template for Twig Component class - Added code completion for Twig Component Groups in YAML config files and AsTwigComponent attribute - Added code completion for Twig Component Types in YAML config files ##### Infrastructure - Redis 7.2+ is now [officially supported](https://doc.ibexa.co/en/4.6/getting_started/requirements/) ##### PHP API The PHP API has been enhanced with the following: PHP API classes and interfaces - [`Ibexa\Contracts\AdminUi\Exception`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/namespaces/ibexa-contracts-adminui-exception.html) - [`Ibexa\Contracts\AdminUi\Exception\UnresolvedPreviewUrlException`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-AdminUi-Exception-UnresolvedPreviewUrlException.html) - [`Ibexa\Contracts\AdminUi\PreviewUrlResolver`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/namespaces/ibexa-contracts-adminui-previewurlresolver.html) - [`Ibexa\Contracts\AdminUi\PreviewUrlResolver\VersionPreviewUrlResolverInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-AdminUi-PreviewUrlResolver-VersionPreviewUrlResolverInterface.html) - [`Ibexa\Contracts\Core\Validation\Constraint`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/namespaces/ibexa-contracts-core-validation-constraint.html) - [`Ibexa\Contracts\Core\Validation\Constraint\UniqueIdentifier`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Validation-Constraint-UniqueIdentifier.html) - [`Ibexa\Contracts\Core\Validation\Constraint\UniqueIdentifierValidator`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Validation-Constraint-UniqueIdentifierValidator.html) - [`Ibexa\Contracts\Messenger`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/namespaces/ibexa-contracts-messenger.html) - [`Ibexa\Contracts\Messenger\Transport`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/namespaces/ibexa-contracts-messenger-transport.html) - [`Ibexa\Contracts\Messenger\Transport\MessageProviderInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Messenger-Transport-MessageProviderInterface.html) - [`Ibexa\Contracts\ProductCatalog\Values\Product\Query\AttributeCriterionBuilder`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/namespaces/ibexa-contracts-productcatalog-values-product-query-attributecriterionbuilder.html) - [`Ibexa\Contracts\ProductCatalog\Values\Product\Query\AttributeCriterionBuilderRegistry`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-AttributeCriterionBuilderRegistry.html) - [`Ibexa\Contracts\ProductCatalog\Values\Product\Query\AttributeCriterionBuilderRegistryInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-AttributeCriterionBuilderRegistryInterface.html) - [`Ibexa\Contracts\ProductCatalog\Values\Product\Query\AttributeCriterionBuilder\AttributeCriterionBuilderInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-AttributeCriterionBuilder-AttributeCriterionBuilderInterface.html) - [`Ibexa\Contracts\ProductCatalog\Values\Product\Query\AttributeCriterionBuilder\CheckboxBuilder`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-AttributeCriterionBuilder-CheckboxBuilder.html) - [`Ibexa\Contracts\ProductCatalog\Values\Product\Query\AttributeCriterionBuilder\ColorBuilder`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-AttributeCriterionBuilder-ColorBuilder.html) - [`Ibexa\Contracts\ProductCatalog\Values\Product\Query\AttributeCriterionBuilder\FloatBuilder`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-AttributeCriterionBuilder-FloatBuilder.html) - [`Ibexa\Contracts\ProductCatalog\Values\Product\Query\AttributeCriterionBuilder\IntegerBuilder`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-AttributeCriterionBuilder-IntegerBuilder.html) - [`Ibexa\Contracts\ProductCatalog\Values\Product\Query\AttributeCriterionBuilder\SelectionBuilder`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-AttributeCriterionBuilder-SelectionBuilder.html) Events - [`Ibexa\Contracts\AdminUi\Event\ResolveVersionPreviewUrlEvent`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-AdminUi-Event-ResolveVersionPreviewUrlEvent.html) #### Full changelog To learn more about all the included changes, see the full release change logs: - [Ibexa Headless v4.6.24](https://github.com/ibexa/headless/releases/tag/v4.6.24) - [Ibexa Experience v4.6.24](https://github.com/ibexa/experience/releases/tag/v4.6.24) - [Ibexa Commerce v4.6.24](https://github.com/ibexa/commerce/releases/tag/v4.6.24) To update your application, see the [update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.6/update_from_4.6/#v4624). ## Ibexa DXP v4.6.23 (Headless, Experience, Commerce, New feature) 2025-08-19 #### Base price column added to a Product Picker view The Product Picker tool that, for example, lets you [select products eligible for discounts](https://doc.ibexa.co/projects/userguide/en/5.0/commerce/discounts/work_with_discounts/#create-new-discount), now displays a **Base price** column for products and product variants. #### Full changelog To learn more about all the included changes, see the full release change logs: - [Ibexa Headless v4.6.23](https://github.com/ibexa/headless/releases/tag/v4.6.23) - [Ibexa Experience v4.6.23](https://github.com/ibexa/experience/releases/tag/v4.6.23) - [Ibexa Commerce v4.6.23](https://github.com/ibexa/commerce/releases/tag/v4.6.23) To update your application, see the [update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.6/update_from_4.6/#v4623). ## Symbol attribute v4.6.22 (Headless, Experience, Commerce, LTS Update, New feature, First release) 2025-08-05 The Symbol attribute allows you to store standardized identifiers of your products in the [product catalog](https://doc.ibexa.co/en/4.6/pim/pim_guide/). For more information, see [Symbol attribute type](https://doc.ibexa.co/en/4.6/pim/attributes/symbol_attribute_type/). #### PHP API The PHP API has been enhanced with the following new classes: - [`Ibexa\Contracts\ProductCatalogSymbolAttribute\Search\Criterion\SymbolAttribute`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalogSymbolAttribute-Search-Criterion-SymbolAttribute.html) - [`Ibexa\Contracts\ProductCatalogSymbolAttribute\Value\ChecksumInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalogSymbolAttribute-Value-ChecksumInterface.html) ## Discounts v4.6.22 (Commerce, LTS Update) 2025-08-05 #### Global discount codes limits - You can now [limit the number of times](https://doc.ibexa.co/en/4.6/discounts/discounts_guide/#discount-codes) a discount code can be used before it expires. The discounts created before this release are set to unlimited global usage #### Discount codes prioritization - Discounts with discount codes now have priority over the other discounts #### Discount codes migrations - You can now create discount codes using [data migrations](https://doc.ibexa.co/en/4.6/content_management/data_migration/importing_data/#discount-codes) #### PHP API The PHP API has been enhanced with the following new classes: - [`Ibexa\Contracts\Discounts\Value\DiscountConditionsInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-DiscountConditionsInterface.html) - [`Ibexa\Contracts\Discounts\Value\Query\SortClause\OverridePrioritization`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-SortClause-OverridePrioritization.html) ## Ibexa DXP v4.6.22 (Headless, Experience, Commerce, New feature) 2025-08-05 #### Special characters in online editor The [online editor](https://doc.ibexa.co/en/4.6/content_management/rich_text/online_editor_guide/) now allows to easily enter special characters like currency symbols. It uses the [special characters plugin](https://ckeditor.com/docs/ckeditor5/latest/features/special-characters.html). *[Image: Special characters in online editor]* #### Support for Solr 9 With this release, Ibexa DXP starts supporting [Solr 9](https://doc.ibexa.co/en/4.6/getting_started/requirements/#search). Solr 9 comes with support for [Dense Vector Search](https://solr.apache.org/guide/solr/latest/query-guide/dense-vector-search.html), paving the way for incoming improvements to the [AI Actions](https://doc.ibexa.co/en/4.6/ai_actions/ai_actions/) feature. #### Improved content creation interface The editing interface of the back office has been improved to better highlight the language, creator, and the publication date when working with content items. *[Image: Improved interface for content creation]* #### Twig Components With the latest changes to [Twig Components](https://doc.ibexa.co/en/4.6/templating/components/), you can: - set component priority when using YAML configuration - render a menu with help of the new Menu component The list of built-in Twig Component groups has been expanded and includes: - one new group for the [back office](https://doc.ibexa.co/en/4.6/administration/back_office/back_office_elements/custom_components/) (`admin-ui-versions-table-before`) - eight new groups for [storefront](https://doc.ibexa.co/en/4.6/templating/layout/customize_storefront_layout/#customize-with-twig-components) #### Taxonomy Subtree limitation You can now manage access to [taxonomy items](https://doc.ibexa.co/en/4.6/content_management/taxonomy/taxonomy/) more effectively by using the new [Taxonomy Subtree limitation](https://doc.ibexa.co/en/4.6/permissions/limitation_reference/#taxonomy-subtree-limitation). In addition, you can now use the [Taxonomy limitation](https://doc.ibexa.co/en/4.6/permissions/limitation_reference/#taxonomy-limitation) together with the `taxonomy/assign` policy. #### Pagination for ezobjectrelationlist in GraphQL To improve performance and gain greater control over the returned responses from the [GraphQL API](https://doc.ibexa.co/en/4.6/api/graphql/graphql/), you can now \[enable pagination\](() of relations specified using the RelationList field type. #### Breaking changes - The `Ibexa\FieldTypeRichText\RichText\Validator\CustomTagsValidator` class has been renamed to `Ibexa\FieldTypeRichText\RichText\Validator\CustomTemplateValidator`, expanding its responsibility to validate both [custom tags](https://doc.ibexa.co/en/4.6/content_management/rich_text/extend_online_editor/#configure-custom-tags) and [custom styles](https://doc.ibexa.co/en/4.6/content_management/rich_text/extend_online_editor/#configure-custom-styles) - The `Ibexa\Contracts\AdminUi\Permission\PermissionCheckContextProviderInterface` interface has been removed - The `Ibexa\Contracts\AdminUi\Values\PermissionCheckContext` class has been removed #### PHP API The PHP API has been enhanced with the following new classes: - [`Ibexa\Contracts\Cart\Exception\VatCalculationExceptionInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Cart-Exception-VatCalculationExceptionInterface.html) - [`Ibexa\Contracts\Core\Repository\Values\Notification\CriterionHandlerInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Notification-CriterionHandlerInterface.html) - [`Ibexa\Contracts\Core\Repository\Values\Notification\Query\CriterionInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Notification-Query-CriterionInterface.html) - [`Ibexa\Contracts\Core\Repository\Values\Notification\Query\Criterion\DateCreated`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Notification-Query-Criterion-DateCreated.html) - [`Ibexa\Contracts\Core\Repository\Values\Notification\Query\NotificationQuery`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Notification-Query-NotificationQuery.html) - [`Ibexa\Contracts\ProductCatalog\Values\Product\Query\Criterion\AbstractPriceRange`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-Criterion-AbstractPriceRange.html) - [`Ibexa\Contracts\ProductCatalog\Values\Product\Query\Criterion\CustomPriceRange`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-Criterion-CustomPriceRange.html) #### Full changelog To learn more about all the included changes, see the full release change logs: - [Ibexa Headless v4.6.22](https://github.com/ibexa/headless/releases/tag/v4.6.22) - [Ibexa Experience v4.6.22](https://github.com/ibexa/experience/releases/tag/v4.6.22) - [Ibexa Commerce v4.6.22](https://github.com/ibexa/commerce/releases/tag/v4.6.22) To update your application, see the [update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.6/update_from_4.6/#v4622). ## Discounts v4.6.21 (Commerce, LTS Update) 2025-06-11 #### REST API - Discounts can now be [managed through the REST API](https://doc.ibexa.co/en/4.6/api/rest_api/rest_api_reference/rest_api_reference.html#discounts) #### PHP API The PHP API has been enhanced with the following new classes: - [`Ibexa\Contracts\Discounts\Exception\DiscountValueResolutionException`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Exception-DiscountValueResolutionException.html) ## Ibexa DXP v4.6.21 (Headless, Experience, Commerce) 2025-06-11 #### Security - This release includes security fixes. To learn more, see the [security advisory IBEXA-SA-2025-003](https://developers.ibexa.co/security-advisories/ibexa-sa-2025-003-xss-vulnerabilities-in-back-office) #### PHP API The PHP API has been enhanced with the following new classes: - [`Ibexa\Contracts\Checkout\Exception\CheckoutException`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Checkout-Exception-CheckoutException.html) - [`Ibexa\Contracts\Checkout\Discounts\DiscountsValidationFailedException`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Checkout-Discounts-DiscountsValidationFailedException.html) #### Full changelog To learn more about all the included changes, see the full release change logs: - [Ibexa Headless v4.6.21](https://github.com/ibexa/headless/releases/tag/v4.6.21) - [Ibexa Experience v4.6.21](https://github.com/ibexa/experience/releases/tag/v4.6.21) - [Ibexa Commerce v4.6.21](https://github.com/ibexa/commerce/releases/tag/v4.6.21) To update your application, see the [update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.6/update_from_4.6/#v4621). ## Discounts v4.6.20 (Commerce, LTS Update) 2025-05-28 #### Features - With the introduction of discount code usage limits, you can now limit the number of times a customer can use a discount code before it becomes invalid - You can now provide your own form themes for the discounts form by using the extension point in [`ibexa_discounts_form_themes` Twig function](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/discounts_twig_functions/#ibexa_discounts_form_themes) #### PHP API The PHP API has been enhanced with the following new classes: - [`Ibexa\Contracts\Discounts\Admin\Form\DiscountValueFormTypeMapperInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-Form-DiscountValueFormTypeMapperInterface.html) - [`Ibexa\Contracts\Discounts\Admin\Form\FormThemeProviderInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-Form-FormThemeProviderInterface.html) - [`Ibexa\Contracts\DiscountsCodes\Exception\DiscountCodeUnusableException`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-DiscountsCodes-Exception-DiscountCodeUnusableException.html) - [`Ibexa\Contracts\DiscountsCodes\Exception\DiscountCodeUserInvalidArgumentException`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-DiscountsCodes-Exception-DiscountCodeUserInvalidArgumentException.html) - [`Ibexa\Contracts\DiscountsCodes\Value\DiscountCodeUsageInterface`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-DiscountsCodes-Value-DiscountCodeUsageInterface.html) - [`Ibexa\Contracts\DiscountsCodes\Value\DiscountCodeUser`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-DiscountsCodes-Value-DiscountCodeUser.html) - [`Ibexa\Contracts\DiscountsCodes\Value\Query\DiscountCodeUsageQuery`](https://doc.ibexa.co/en/latest/api/php_api/php_api_reference/classes/Ibexa-Contracts-DiscountsCodes-Value-Query-DiscountCodeUsageQuery.html) To update to the latest version, see the [update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.6/update_from_4.6/#lts-updates). ## Ibexa DXP v4.6.20 (Headless, Experience, Commerce) 2025-05-27 #### Twig Components - The new [Twig Components](https://doc.ibexa.co/en/latest/templating/components/) feature allow you to effortlessly build customizable and reusable Twig templates in Ibexa DXP #### Extending Sub-items view - Thanks to the new extension point, you can now [add new views or overwrite existing ones in the Sub-items list](https://doc.ibexa.co/en/latest/administration/back_office/subitems_list/#create-custom-sub-items-list-view) #### Infrastructure - MySQL 8.4, Node 20 and Node 22 are now [officially supported](https://doc.ibexa.co/en/latest/getting_started/requirements/) #### PHP API The PHP API has been enhanced with the following new classes: - [`Ibexa\Contracts\AdminUi\Menu\AbstractActionBuilder`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-AdminUi-Menu-AbstractActionBuilder.html) - [`Ibexa\Contracts\TwigComponents\ComponentInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-TwigComponents-ComponentInterface.html) - [`Ibexa\Contracts\TwigComponents\ComponentRegistryInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-TwigComponents-ComponentRegistryInterface.html) - [`Ibexa\Contracts\TwigComponents\Event\RenderGroupEvent`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-TwigComponents-Event-RenderGroupEvent.html) - [`Ibexa\Contracts\TwigComponents\Event\RenderSingleEvent`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-TwigComponents-Event-RenderSingleEvent.html) - [`Ibexa\Contracts\TwigComponents\Exception\InvalidArgumentException`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-TwigComponents-Exception-InvalidArgumentException.html) - [`Ibexa\Contracts\TwigComponents\Renderer\RendererInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-TwigComponents-Renderer-RendererInterface.html) #### Full changelog To learn more about all the included changes, see the full release change logs: - [Ibexa Headless v4.6.20](https://github.com/ibexa/headless/releases/tag/v4.6.20) - [Ibexa Experience v4.6.20](https://github.com/ibexa/experience/releases/tag/v4.6.20) - [Ibexa Commerce v4.6.20](https://github.com/ibexa/commerce/releases/tag/v4.6.20) To update your application, see the [update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.6/update_from_4.6/#v4620). ## Discounts v4.6.19 (Commerce, LTS Update, New feature, First release) 2025-04-09 With the Discounts LTS Update, you can temporarily or permanently reduce prices on specific products or categories, making deals more attractive to potential buyers. Use them to encourage first-time purchases, reward loyal customers, promote new or slow-moving items, or drive sales during seasonal events. By displaying discounted prices clearly in the catalog or cart, businesses can create a sense of urgency, increase customer satisfaction, and ultimately boost revenue. *[Image: Discounts for products in the cart]* For more information, see [Discounts product guide](https://doc.ibexa.co/en/4.6/discounts/discounts_guide/). ## AI Actions v4.6.19 (Headless, Experience, Commerce, LTS Update, New feature) 2025-04-09 #### Features AI Actions can now integrate with [Ibexa Connect](https://doc.ibexa.co/projects/connect/en/latest), giving you an opportunity to build complex data transformation workflows without having to rely on custom code. To learn more, see the [setup instructions for this integration](https://doc.ibexa.co/en/4.6/ai_actions/install_ai_actions/#configure-access-to-ibexa-connect). ## Ibexa DXP v4.6.19 (Headless, Experience, Commerce) 2025-04-09 #### Security - This release includes security fixes. To learn more, see the [published security advisory IBEXA-SA-2025-002](https://developers.ibexa.co/security-advisories/ibexa-sa-2025-002-xxe-vulnerability-in-richtext) #### Features - The [CartSummary endpoint](https://doc.ibexa.co/en/4.6/api/rest_api/rest_api_reference/rest_api_reference.html#managing-commerce-carts-cart-summary) now supports a new `Accept` header: `application/vnd.ibexa.api.ShortCartSummary`, returning only the essential data about products in the cart - Added a new repository setting: [grace period for archived versions](https://doc.ibexa.co/en/latest/administration/configuration/repository_configuration/#grace-period-for-archived-versions) - Added a new `group_remote_id` setting for [controlling the user group in which registering users are created](https://doc.ibexa.co/en/latest/users/user_registration/#user-groups) #### Ibexa Rector - The [Ibexa Rector package](https://github.com/ibexa/rector/tree/4.6?tab=readme-ov-file#ibexa-dxp-rector) has been released, allowing you to automatically refactor your code and remove deprecations. To learn how to use it, see the [update instructions](https://doc.ibexa.co/en/latest/update_and_migration/from_4.6/update_from_4.6/#ibexa-rector) #### PHP API The PHP API has been enhanced with the following new classes: - [`Ibexa\Contracts\Connect\Ai\ActionHandlerDataStructureAwareInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Ai-ActionHandlerDataStructureAwareInterface.html) - [`Ibexa\Contracts\Connect\Resource\CustomPropertyStructure\CustomPropertyStructureCreateStruct`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-CustomPropertyStructure-CustomPropertyStructureCreateStruct.html) - [`Ibexa\Contracts\Connect\Resource\CustomPropertyStructure\CustomPropertyStructureFilter`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-CustomPropertyStructure-CustomPropertyStructureFilter.html) - [`Ibexa\Contracts\Connect\Resource\CustomPropertyStructure\CustomPropertyStructureItemCreateStruct`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-CustomPropertyStructure-CustomPropertyStructureItemCreateStruct.html) - [`Ibexa\Contracts\Connect\Resource\CustomPropertyStructureInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-CustomPropertyStructureInterface.html) - [`Ibexa\Contracts\Connect\Resource\Scenario\CustomPropertiesDataFillInStruct`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-Scenario-CustomPropertiesDataFillInStruct.html) - [`Ibexa\Contracts\Connect\Response\CustomPropertyStructure\CreateItemResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-CustomPropertyStructure-CreateItemResponse.html) - [`Ibexa\Contracts\Connect\Response\CustomPropertyStructure\CreateResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-CustomPropertyStructure-CreateResponse.html) - [`Ibexa\Contracts\Connect\Response\CustomPropertyStructure\DeleteItemResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-CustomPropertyStructure-DeleteItemResponse.html) - [`Ibexa\Contracts\Connect\Response\CustomPropertyStructure\ListItemResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-CustomPropertyStructure-ListItemResponse.html) - [`Ibexa\Contracts\Connect\Response\CustomPropertyStructure\ListResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-CustomPropertyStructure-ListResponse.html) - [`Ibexa\Contracts\Connect\Response\CustomPropertyStructure\RetrieveItemResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-CustomPropertyStructure-RetrieveItemResponse.html) - [`Ibexa\Contracts\Connect\Response\CustomPropertyStructure\RetrieveResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-CustomPropertyStructure-RetrieveResponse.html) - [`Ibexa\Contracts\Connect\Response\Scenario\RetrieveCustomPropertiesDataResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-Scenario-RetrieveCustomPropertiesDataResponse.html) - [`Ibexa\Contracts\Core\Repository\Events\Notification\BeforeMarkNotificationAsUnreadEvent`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Events-Notification-BeforeMarkNotificationAsUnreadEvent.html) - [`Ibexa\Contracts\Core\Repository\Events\Notification\MarkNotificationAsUnreadEvent`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Events-Notification-MarkNotificationAsUnreadEvent.html) - [`Ibexa\Contracts\ProductCatalog\CustomerGroupAssignedItemsServiceDecorator`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-CustomerGroupAssignedItemsServiceDecorator.html) - [`Ibexa\Contracts\ProductCatalog\CustomerGroupAssignedItemsServiceInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-CustomerGroupAssignedItemsServiceInterface.html) - [`Ibexa\Contracts\ProductCatalog\Events\CustomerGroupCanBeDeletedEvent`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Events-CustomerGroupCanBeDeletedEvent.html) - [`Ibexa\Contracts\ProductCatalog\Values\CustomerGroup\AssignedItem`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-CustomerGroup-AssignedItem.html) - [`Ibexa\Contracts\ProductCatalog\Values\CustomerGroup\AssignedItemInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-CustomerGroup-AssignedItemInterface.html) #### Full changelog To learn more about all the included changes, see the full release change logs: - [Ibexa Headless v4.6.19](https://github.com/ibexa/headless/releases/tag/v4.6.19) - [Ibexa Experience v4.6.19](https://github.com/ibexa/experience/releases/tag/v4.6.19) - [Ibexa Commerce v4.6.19](https://github.com/ibexa/commerce/releases/tag/v4.6.19) To update your application, see the [update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.6/update_from_4.6/#v4619). ## Ibexa DXP v4.6.18 (Headless, Experience, Commerce) 2025-03-06 #### PHP API The PHP API has been enhanced with the following new classes: - [`Ibexa\Contracts\ProductCatalog\Form\Data\ProductSelectorData`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Form-Data-ProductSelectorData.html) - [`Ibexa\Contracts\ProductCatalog\Form\Data\ProductsSelectorData`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Form-Data-ProductsSelectorData.html) - [`Ibexa\Contracts\ProductCatalog\Form\Type\ProductSelectorType`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Form-Type-ProductSelectorType.html) #### Full changelog To learn more about all the included changes, see the full release change logs: - [Ibexa Headless v4.6.18](https://github.com/ibexa/headless/releases/tag/v4.6.18) - [Ibexa Experience v4.6.18](https://github.com/ibexa/experience/releases/tag/v4.6.18) - [Ibexa Commerce v4.6.18](https://github.com/ibexa/commerce/releases/tag/v4.6.18) To update your application, see the [update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.6/update_from_4.6/#v4618). ## Date and time attribute v4.6.18 (Headless, Experience, Commerce, LTS Update, New feature, First release) 2025-03-04 The Date and time attributes allow you to represent date and time values as part of the product specification in the [product catalog](https://doc.ibexa.co/en/4.6/pim/pim_guide/). For more information, see [Date and time attributes](https://doc.ibexa.co/en/4.6/pim/attributes/date_and_time/). ## AI Actions v4.6.17 (Headless, Experience, Commerce, LTS Update, New feature) 2025-03-04 #### Features You can now [duplicate AI actions](https://doc.ibexa.co/projects/userguide/en/5.0/ai_actions/work_with_ai_actions/#duplicate-ai-actions) in the AI actions list. #### PHP API The PHP API has been expanded with the following classes and interfaces: - [`Ibexa\Contracts\ConnectorAi\ActionConfiguration\ActionConfigurationCopyStruct`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-ActionConfigurationCopyStruct.html) - [`Ibexa\Contracts\ConnectorAi\ActionHandlerRegistryInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionHandlerRegistryInterface.html) - [`Ibexa\Contracts\ConnectorAi\Prompt\PromptFactory`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Prompt-PromptFactory.html) - [`Ibexa\Contracts\ConnectorAi\Prompt\PromptInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Prompt-PromptInterface.html) - [`Ibexa\Contracts\ConnectorAi\PromptResolverInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-PromptResolverInterface.html) ## Ibexa DXP v4.6.17 (Headless, Experience, Commerce, New feature) 2025-03-04 #### Security This release includes security fixes. To learn more, see the [corresponding security advisory](https://developers.ibexa.co/security-advisories/ibexa-sa-2025-001-vulnerabilities-in-shopping-cart-and-publish-unscheduling). #### Features - New REST API endpoints for [Segments](https://doc.ibexa.co/en/4.6/api/rest_api/rest_api_reference/rest_api_reference.html#segments) and [Segment Groups](https://doc.ibexa.co/en/4.6/api/rest_api/rest_api_reference/rest_api_reference.html#segment-groups) - PHP API Client ([`Ibexa\Contracts\Connect\ConnectClientInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-ConnectClientInterface.html)) for [Ibexa Connect](https://doc.ibexa.co/projects/connect/en/latest) - The following Twig functions now additionally support objects implementing the [`ContentAwareInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-ContentAwareInterface.html) as arguments: - [`ibexa_content_field_identifier_first_filled_image`](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/image_twig_functions/#ibexa_content_field_identifier_first_filled_image) - [`ibexa_content_name`](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/content_twig_functions/#ibexa_content_name) - [`ibexa_field_is_empty`](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/field_twig_functions/#ibexa_field_is_empty) - [`ibexa_field_description`](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/field_twig_functions/#ibexa_field_description) - [`ibexa_field_name`](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/field_twig_functions/#ibexa_field_name) - [`ibexa_field_value`](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/field_twig_functions/#ibexa_field_value) - [`ibexa_field`](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/field_twig_functions/#ibexa_field) - [`ibexa_has_field`](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/field_twig_functions/#ibexa_has_field) - [`ibexa_render_field`](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/field_twig_functions/#ibexa_render_field) - [`ibexa_seo_is_empty`](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/content_twig_functions/#ibexa_seo_is_empty) - [`ibexa_seo`](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/content_twig_functions/#ibexa_seo) - [`ibexa_taxonomy_entries_for_content`](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/content_twig_functions/#ibexa_taxonomy_entries_for_content-filter) - Added new Twig filter for product attributes grouping: [`ibexa_product_catalog_group_attributes`](https://doc.ibexa.co/en/4.6/templating/twig_function_reference/product_twig_functions/#ibexa_product_catalog_group_attributes) #### PHP API The PHP API has been enhanced with the following new classes and interfaces: - `Ibexa\Contracts\Cart`: - [`Value\Query\Criterion\LogicalAnd`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Cart-Value-Query-Criterion-LogicalAnd.html) - [`Value\Query\Criterion\OwnerCriterion`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Cart-Value-Query-Criterion-OwnerCriterion.html) - [`Value\Query\CriterionInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Cart-Value-Query-CriterionInterface.html) - `Ibexa\Contracts\Segmentation`: - [`Exception\ValidationFailedExceptionInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Segmentation-Exception-ValidationFailedExceptionInterface.html) - `Ibexa\Contracts\ProductCatalog`: - [`Iterator\BatchIteratorAdapter\RegionFetchAdapter`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Iterator-BatchIteratorAdapter-RegionFetchAdapter.html) - `Ibexa\Contracts\Connect`: - [`ConnectClientInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-ConnectClientInterface.html) - [`Exception\BadResponseException`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Exception-BadResponseException.html) - [`Exception\UnserializablePayload`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Exception-UnserializablePayload.html) - [`Exception\UnserializableResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Exception-UnserializableResponse.html) - [`PaginationInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-PaginationInterface.html) - [`Resource\DataStructure\DataStructureBuilder`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-DataStructure-DataStructureBuilder.html) - [`Resource\DataStructure\DataStructureCreateStruct`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-DataStructure-DataStructureCreateStruct.html) - [`Resource\DataStructure\DataStructureFilter`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-DataStructure-DataStructureFilter.html) - [`Resource\DataStructure\DataStructureProperty`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-DataStructure-DataStructureProperty.html) - [`Resource\DataStructure\DataStructurePropertyType`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-DataStructure-DataStructurePropertyType.html) - [`Resource\DataStructureInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-DataStructureInterface.html) - [`Resource\Hook\HookCreateStruct`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-Hook-HookCreateStruct.html) - [`Resource\Hook\HookFilter`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-Hook-HookFilter.html) - [`Resource\Hook\HookSetDetailsStruct`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-Hook-HookSetDetailsStruct.html) - [`Resource\HookInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-HookInterface.html) - [`Resource\Scenario\ScenarioCreateStruct`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-Scenario-ScenarioCreateStruct.html) - [`Resource\Scenario\ScenarioFilter`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-Scenario-ScenarioFilter.html) - [`Resource\ScenarioInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-ScenarioInterface.html) - [`Resource\Team\TeamVariableCreateStruct`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-Team-TeamVariableCreateStruct.html) - [`Resource\Team\TeamVariableFilter`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-Team-TeamVariableFilter.html) - [`Resource\Team\TeamVariableUpdateStruct`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-Team-TeamVariableUpdateStruct.html) - [`Resource\TeamInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-TeamInterface.html) - [`Resource\Template\TemplateCreateStruct`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-Template-TemplateCreateStruct.html) - [`Resource\Template\TemplateFilter`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-Template-TemplateFilter.html) - [`Resource\TemplateInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Resource-TemplateInterface.html) - [`Response\DataStructure\CreateResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-DataStructure-CreateResponse.html) - [`Response\DataStructure\ListResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-DataStructure-ListResponse.html) - [`Response\DataStructure\RetrieveResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-DataStructure-RetrieveResponse.html) - [`Response\Hook\CreateResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-Hook-CreateResponse.html) - [`Response\Hook\ListResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-Hook-ListResponse.html) - [`Response\Hook\RetrieveResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-Hook-RetrieveResponse.html) - [`Response\Hook\SetDetailsResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-Hook-SetDetailsResponse.html) - [`Response\Scenario\CreateResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-Scenario-CreateResponse.html) - [`Response\Scenario\ListResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-Scenario-ListResponse.html) - [`Response\Scenario\RetrieveResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-Scenario-RetrieveResponse.html) - [`Response\Team\TeamVariableCreateResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-Team-TeamVariableCreateResponse.html) - [`Response\Team\TeamVariableListResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-Team-TeamVariableListResponse.html) - [`Response\Team\TeamVariableRetrieveResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-Team-TeamVariableRetrieveResponse.html) - [`Response\Team\TeamVariableUpdateResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-Team-TeamVariableUpdateResponse.html) - [`Response\Template\BlueprintResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-Template-BlueprintResponse.html) - [`Response\Template\CreateResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-Template-CreateResponse.html) - [`Response\Template\ListResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-Template-ListResponse.html) - [`Response\Template\RetrieveResponse`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Response-Template-RetrieveResponse.html) - [`ResponseInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-ResponseInterface.html) - [`TransportInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-TransportInterface.html) - [`Value\Blueprint\Flow`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Value-Blueprint-Flow.html) - [`Value\Blueprint\Metadata\Scenario`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Value-Blueprint-Metadata-Scenario.html) - [`Value\Blueprint\Metadata`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Value-Blueprint-Metadata.html) - [`Value\Blueprint\Module\CustomWebhook`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Value-Blueprint-Module-CustomWebhook.html) - [`Value\Blueprint\Module\JsonCreate`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Value-Blueprint-Module-JsonCreate.html) - [`Value\Blueprint\Module\ModuleDesigner`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Value-Blueprint-Module-ModuleDesigner.html) - [`Value\Blueprint\Module\WebhookRespond`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Value-Blueprint-Module-WebhookRespond.html) - [`Value\Blueprint`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Value-Blueprint.html) - [`Value\Controller`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Value-Controller.html) - [`Value\Scheduling`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connect-Value-Scheduling.html) #### Full changelog To learn more about all the included changes, see the full release change logs: - [Ibexa Headless v4.6.17](https://github.com/ibexa/headless/releases/tag/v4.6.17) - [Ibexa Experience v4.6.17](https://github.com/ibexa/experience/releases/tag/v4.6.17) - [Ibexa Commerce v4.6.17](https://github.com/ibexa/commerce/releases/tag/v4.6.17) To update your application, see the [update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.6/update_from_4.6/#v4617). ## AI Actions v4.6.16 (Headless, Experience, Commerce, LTS Update, New feature) 2025-01-16 #### Features The new AI Assistant allows you to use the AI capabilities in additional places, including RichText, Text line, Text Block fields, and certain Page Builder blocks. ## Ibexa DXP v4.6.16 (Headless, Experience, Commerce) 2025-01-16 #### PHP API The PHP API has been enhanced with the following new classes: - [`Ibexa\Contracts\AdminUi\Permission\PermissionCheckContextProviderInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-AdminUi-Permission-PermissionCheckContextProviderInterface.html) - [`Ibexa\Contracts\AdminUi\Values\PermissionCheckContext`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-AdminUi-Values-PermissionCheckContext.html) - [`Ibexa\Contracts\Checkout\Discounts\DataMapper\DiscountsDataMapperInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Checkout-Discounts-DataMapper-DiscountsDataMapperInterface.html) - [`Ibexa\Contracts\Seo\Resolver\FieldValueResolverInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Seo-Resolver-FieldValueResolverInterface.html) #### Full changelog To learn more about all the included changes, see the full release change logs: - [Ibexa Headless v4.6.16](https://github.com/ibexa/headless/releases/tag/v4.6.16) - [Ibexa Experience v4.6.16](https://github.com/ibexa/experience/releases/tag/v4.6.16) - [Ibexa Commerce v4.6.16](https://github.com/ibexa/commerce/releases/tag/v4.6.16) To update your application, see the [update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.6/update_from_4.6/#v4616). ## AI Actions v4.6.15 (Headless, Experience, Commerce, LTS Update, New feature) 2024-12-13 #### REST API The REST API has been extended to include endpoints for: - [Action Configurations](https://doc.ibexa.co/en/4.6/api/rest_api/rest_api_reference/rest_api_reference.html#ai-actions-list-action-configurations) - [Action Types](https://doc.ibexa.co/en/4.6/api/rest_api/rest_api_reference/rest_api_reference.html#ai-actions-list-action-types) ## Ibexa DXP v4.6.15 (Headless, Experience, Commerce, New feature) 2024-12-13 ### Features You can now reuse Page Builder blocks between landing pages using the ["Copy block" action](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/create_edit_pages/#copy-blocks). #### PHP API The PHP API has been enhanced with the following new classes and interfaces: - [`Ibexa\Contracts\ProductCatalog\Values\Price\PriceEnvelopeInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Price-PriceEnvelopeInterface.html) - [`Ibexa\Contracts\ProductCatalog\Values\Price\PriceStampInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Price-PriceStampInterface.html) - [`Ibexa\Contracts\ProductCatalog\Values\StampInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-StampInterface.html) #### Full changelog To learn more about all the included changes, see the full release change logs: - [Ibexa Headless v4.6.15](https://github.com/ibexa/headless/releases/tag/v4.6.15) - [Ibexa Experience v4.6.15](https://github.com/ibexa/experience/releases/tag/v4.6.15) - [Ibexa Commerce v4.6.15](https://github.com/ibexa/commerce/releases/tag/v4.6.15) To update your application, see the [update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.6/update_from_4.6/#v4615). ## Ibexa DXP v4.6.14 (Headless, Experience, Commerce) 2024-11-28 #### Security This release includes security fixes. To learn more, see the [corresponding security advisory](https://developers.ibexa.co/security-advisories/ibexa-sa-2024-006-vulnerabilities-in-content-name-pattern-commerce-shop-and-varnish-vhost-templates). #### UX Improvements - The identifiers for content types and field definitions are now autogenerated based on the provided name - You can now search in [Trash](https://doc.ibexa.co/projects/userguide/en/5.0/content_management/content_organization/copy_move_hide_content/#remove-content) by content's name #### Search - New search criterion: [IsUserEnabled](https://doc.ibexa.co/en/4.6/search/criteria_reference/isuserenabled_criterion/) #### PHP API The PHP API has been enhanced with the following new classes and interfaces: - [`Ibexa\Contracts\Core\Validation\AbstractValidationStructWrapper`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Validation-AbstractValidationStructWrapper.html) - [`Ibexa\Contracts\Core\Validation\StructValidator`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Validation-StructValidator.html) - [`Ibexa\Contracts\Core\Validation\StructWrapperValidator`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Validation-StructWrapperValidator.html) - [`Ibexa\Contracts\Core\Validation\ValidationFailedException`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Validation-ValidationFailedException.html) - [`Ibexa\Contracts\Core\Validation\ValidationStructWrapperInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Validation-ValidationStructWrapperInterface.html) - [`Ibexa\Contracts\Notifications\SystemNotification\SystemMessage`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Notifications-SystemNotification-SystemMessage.html) - [`Ibexa\Contracts\Notifications\SystemNotification\SystemNotification`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Notifications-SystemNotification-SystemNotification.html) - [`Ibexa\Contracts\Notifications\SystemNotification\SystemNotificationInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Notifications-SystemNotification-SystemNotificationInterface.html) - [`Ibexa\Contracts\Notifications\Value\Recipent\UserRecipientInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Notifications-Value-Recipent-UserRecipientInterface.html) - [`Ibexa\Contracts\ProductCatalog\ProductReferencesResolverStrategy`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-ProductReferencesResolverStrategy.html) - [`Ibexa\Contracts\ProductCatalog\Values\Product\Query\Criterion\UpdatedAt`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-Criterion-UpdatedAt.html) - [`Ibexa\Contracts\ProductCatalog\Values\Product\Query\Criterion\UpdatedAtRange`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-Criterion-UpdatedAtRange.html) #### Full changelog To learn more about all the included changes, see the full release change logs: - [Ibexa Headless v4.6.14](https://github.com/ibexa/headless/releases/tag/v4.6.14) - [Ibexa Experience v4.6.14](https://github.com/ibexa/experience/releases/tag/v4.6.14) - [Ibexa Commerce v4.6.14](https://github.com/ibexa/commerce/releases/tag/v4.6.14) To update your application, see the [update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.6/update_from_4.6/#v4614). ## Ibexa DXP v4.6.13 (Headless, Experience, Commerce) 2024-10-22 #### PHP API The PHP API has been enhanced with the following new classes and interfaces: - [Ibexa\\Contracts\\CoreSearch\\Persistence\\CriterionMapper\\AbstractCompositeCriterionMapper](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-CoreSearch-Persistence-CriterionMapper-AbstractCompositeCriterionMapper.html) - [Ibexa\\Contracts\\CoreSearch\\Persistence\\CriterionMapper\\AbstractFieldCriterionMapper](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-CoreSearch-Persistence-CriterionMapper-AbstractFieldCriterionMapper.html) - [Ibexa\\Contracts\\Rest\\Output\\Exceptions\\AbstractExceptionVisitor](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Rest-Output-Exceptions-AbstractExceptionVisitor.html) - [Ibexa\\Contracts\\CoreSearch\\Values\\Query\\CriterionMapper](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-CoreSearch-Values-Query-CriterionMapper.html) #### Full changelog To learn more about all the included changes, see the full release change logs: - [Ibexa Headless v4.6.13](https://github.com/ibexa/headless/releases/tag/v4.6.13) - [Ibexa Experience v4.6.13](https://github.com/ibexa/experience/releases/tag/v4.6.13) - [Ibexa Commerce v4.6.13](https://github.com/ibexa/commerce/releases/tag/v4.6.13) To update your application, see the [update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.6/update_from_4.6/#v4613). ## AI Actions v4.6.12 (Headless, Experience, Commerce, LTS Update, New feature, First release) 2024-10-04 The AI Actions LTS update enhances the usability and flexibility of Ibexa DXP v4.6 LTS by harnessing the potential of artificial intelligence to automate time-consuming editorial tasks. By default, the AI Actions feature can help users with their work in following scenarios: - Refining text: when editing a content item, users can request that a passage selected in online editor is modified, for example, by adjusting the length of the text, changing its tone, or correcting linguistic errors. - Generating alternative text: when working with images, users can ask AI to generate alternative text for them, which helps improve accessibility and SEO. *[Image: AI Assistant]* For more information, see [AI Actions product guide](https://doc.ibexa.co/en/4.6/ai_actions/ai_actions_guide/). ## Ibexa DXP v4.6.12 (Headless, Experience, Commerce) 2024-10-04 #### PHP API The PHP API has been enhanced with the following new classes and interfaces: - [Ibexa\\Contracts\\AdminUi\\Menu\\AbstractFormContextMenuBuilder](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-AdminUi-Menu-AbstractFormContextMenuBuilder.html) - [Ibexa\\Contracts\\AdminUi\\Menu\\CopyFormContextMenuBuilder](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-AdminUi-Menu-CopyFormContextMenuBuilder.html) - [Ibexa\\Contracts\\AdminUi\\Menu\\CreateFormContextMenuBuilder](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-AdminUi-Menu-CreateFormContextMenuBuilder.html) - [Ibexa\\Contracts\\AdminUi\\Menu\\MenuItemFactoryInterface](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-AdminUi-Menu-MenuItemFactoryInterface.html) - [Ibexa\\Contracts\\AdminUi\\Menu\\UpdateFormContextMenuBuilder](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-AdminUi-Menu-UpdateFormContextMenuBuilder.html) - [Ibexa\\Contracts\\Core\\Pool\\Pool](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Pool-Pool.html) - [Ibexa\\Contracts\\Core\\Pool\\PoolInterface](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Pool-PoolInterface.html) - [Ibexa\\Contracts\\CoreSearch\\Values\\Query\\AbstractCriterionQuery](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-CoreSearch-Values-Query-AbstractCriterionQuery.html) - [Ibexa\\Contracts\\CoreSearch\\Values\\Query\\AbstractSortClause](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-CoreSearch-Values-Query-AbstractSortClause.html) - [Ibexa\\Contracts\\CoreSearch\\Values\\Query\\Criterion\\AbstractCompositeCriterion](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-CoreSearch-Values-Query-Criterion-AbstractCompositeCriterion.html) - [Ibexa\\Contracts\\CoreSearch\\Values\\Query\\Criterion\\CriterionInterface](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-CoreSearch-Values-Query-Criterion-CriterionInterface.html) - [Ibexa\\Contracts\\CoreSearch\\Values\\Query\\Criterion\\FieldValueCriterion](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-CoreSearch-Values-Query-Criterion-FieldValueCriterion.html) - [Ibexa\\Contracts\\CoreSearch\\Values\\Query\\Criterion\\LogicalAnd](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-CoreSearch-Values-Query-Criterion-LogicalAnd.html) - [Ibexa\\Contracts\\CoreSearch\\Values\\Query\\Criterion\\LogicalOr](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-CoreSearch-Values-Query-Criterion-LogicalOr.html) - [Ibexa\\Contracts\\CoreSearch\\Values\\Query\\CriterionMapper](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-CoreSearch-Values-Query-CriterionMapper.html) - [Ibexa\\Contracts\\CoreSearch\\Values\\Query\\CriterionMapperInterface](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-CoreSearch-Values-Query-CriterionMapperInterface.html) - [Ibexa\\Contracts\\CoreSearch\\Values\\Query\\SortClause\\FieldValueSortClause](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-CoreSearch-Values-Query-SortClause-FieldValueSortClause.html) - [Ibexa\\Contracts\\CoreSearch\\Values\\Query\\SortDirection](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-CoreSearch-Values-Query-SortDirection.html) - [Ibexa\\Contracts\\ProductCatalog\\Local\\Attribute\\ContextAwareValueValidatorInterface](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Local-Attribute-ContextAwareValueValidatorInterface.html) #### Full changelog To learn more about all the included changes, see the full release change logs: - [Ibexa Headless v4.6.12](https://github.com/ibexa/headless/releases/tag/v4.6.12) - [Ibexa Experience v4.6.12](https://github.com/ibexa/experience/releases/tag/v4.6.12) - [Ibexa Commerce v4.6.12](https://github.com/ibexa/commerce/releases/tag/v4.6.12) To update your application, see the [update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.6/update_from_4.6/#v4612). ## Ibexa DXP v4.6.11 (Headless, Experience, Commerce) 2024-09-16 #### Search - New search criterion: [`IsBookmarked`](https://doc.ibexa.co/en/4.6/search/criteria_reference/isbookmarked_criterion/) #### PHP API The PHP API has been enhanced with the following new classes and interfaces: - [`Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion\Location\IsBookmarked`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-Query-Criterion-Location-IsBookmarked.html) And the new methods are: - [`Ibexa\Contracts\Core\Persistence\Bookmark\Handler::loadUserIdsByLocation()`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Persistence-Bookmark-Handler.html#method_loadUserIdsByLocation) - [`Ibexa\Contracts\ProductCatalog\Local\LocalProductTypeServiceDecorator::addContentTypeFieldDefinition()`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Local-LocalProductTypeServiceDecorator.html#method_addContentTypeFieldDefinition) - [`Ibexa\Contracts\ProductCatalog\Local\LocalProductTypeServiceDecorator::removeContentTypeFieldDefinition()`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Local-LocalProductTypeServiceDecorator.html#method_removeContentTypeFieldDefinition) - [`Ibexa\Contracts\ProductCatalog\Local\LocalProductTypeServiceInterface::addContentTypeFieldDefinition()`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Local-LocalProductTypeServiceInterface.html#methods) - [`Ibexa\Contracts\ProductCatalog\Local\LocalProductTypeServiceInterface::removeContentTypeFieldDefinition()`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Local-LocalProductTypeServiceInterface.html#method_removeContentTypeFieldDefinition) To learn more about all the included changes, see the full release change logs: - [Ibexa Headless v4.6.11](https://github.com/ibexa/headless/releases/tag/v4.6.11) - [Ibexa Experience v4.6.11](https://github.com/ibexa/experience/releases/tag/v4.6.11) - [Ibexa Commerce v4.6.11](https://github.com/ibexa/commerce/releases/tag/v4.6.11) To update your application, see the [update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.6/update_from_4.6/#v4611). ## Ibexa DXP v4.6.10 (Headless, Experience, Commerce) 2024-08-14 #### Security This release includes security fixes. To learn more, see the [corresponding security advisory](https://developers.ibexa.co/security-advisories/ibexa-sa-2024-005-persistent-xss-in-richtext). To learn more about all the included changes, see the full release change logs: - [Ibexa Headless v4.6.10](https://github.com/ibexa/headless/releases/tag/v4.6.10) - [Ibexa Experience v4.6.10](https://github.com/ibexa/experience/releases/tag/v4.6.10) - [Ibexa Commerce v4.6.10](https://github.com/ibexa/commerce/releases/tag/v4.6.10) To update your application, see the [update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.6/update_from_4.6/#v4610). ## Ibexa DXP v4.6.9 (Headless, Experience, Commerce) 2024-07-31 #### Security This release includes security fixes. To learn more, see the [corresponding security advisory](https://developers.ibexa.co/security-advisories/ibexa-sa-2024-004-dom-based-xss-in-file-upload). #### PHP API The PHP API has been enhanced with the following new classes and interfaces: - [`Ibexa\Contracts\ConnectorQualifio\Exception\QualifioException`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorQualifio-Exception-QualifioException.html) - [`Ibexa\Contracts\ConnectorQualifio\Exception\CampaignFeedNotFoundException`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorQualifio-Exception-CampaignFeedNotFoundException.html) - [`Ibexa\Contracts\ConnectorQualifio\Exception\CommunicationException`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorQualifio-Exception-CommunicationException.html) - [`Ibexa\Contracts\ConnectorQualifio\Exception\NotConfiguredException`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorQualifio-Exception-NotConfiguredException.html) To learn more about all the included changes, see the full release change logs: - [Ibexa Headless v4.6.9](https://github.com/ibexa/headless/releases/tag/v4.6.9) - [Ibexa Experience v4.6.9](https://github.com/ibexa/experience/releases/tag/v4.6.9) - [Ibexa Commerce v4.6.9](https://github.com/ibexa/commerce/releases/tag/v4.6.9) To update your application, see the [update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.6/update_from_4.6/#v469). ## Ibexa DXP v4.6.8 (Headless, Experience, Commerce) 2024-07-11 #### PHP API The PHP API has been enhanced with the following new class: - [`Ibexa\Contracts\FieldTypeRichText\Configuration\ProviderConfiguratorInterface`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-FieldTypeRichText-Configuration-ProviderConfiguratorInterface.html) #### Full changelog To learn more about all the included changes, see the full release change logs: - [Ibexa Headless v4.6.8](https://github.com/ibexa/headless/releases/tag/v4.6.8) - [Ibexa Experience v4.6.8](https://github.com/ibexa/experience/releases/tag/v4.6.8) - [Ibexa Commerce v4.6.8](https://github.com/ibexa/commerce/releases/tag/v4.6.8) To update your application, see the [update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.6/update_from_4.6/#v468). ## Ibexa DXP v4.6.7 (Headless, Experience, Commerce) 2024-06-10 #### PHP API The PHP API has been enhanced with the following new classes: - [`Ibexa\Contracts\Calendar\EventAction\EventActionCollection`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Calendar-EventAction-EventActionCollection.html) - [`Ibexa\Contracts\Calendar\EventSource\InMemoryEventSource`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Calendar-EventSource-InMemoryEventSource.html) - [`Ibexa\Contracts\Core\Event\Mapper\ResolveMissingFieldEvent`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Event-Mapper-ResolveMissingFieldEvent.html) - [`Ibexa\Contracts\Core\FieldType\DefaultDataFieldStorage`](https://doc.ibexa.co/en/4.6/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-FieldType-DefaultDataFieldStorage.html) #### Full changelog To learn more about all the included changes, see the full release change logs: - [Ibexa Headless v4.6.7](https://github.com/ibexa/headless/releases/tag/v4.6.7) - [Ibexa Experience v4.6.7](https://github.com/ibexa/experience/releases/tag/v4.6.7) - [Ibexa Commerce v4.6.7](https://github.com/ibexa/commerce/releases/tag/v4.6.7) To update your application, see the [update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.6/update_from_4.6/#v467). ## Ibexa DXP v4.6.6 (Headless, Experience, Commerce) 2024-05-17 To learn more about all the included changes, see the full release change logs: - [Ibexa Headless v4.6.6](https://github.com/ibexa/headless/releases/tag/v4.6.6) - [Ibexa Experience v4.6.6](https://github.com/ibexa/experience/releases/tag/v4.6.6) - [Ibexa Commerce v4.6.6](https://github.com/ibexa/commerce/releases/tag/v4.6.6) To update your application, see the [update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.6/update_from_4.6/#v466). ## Ibexa DXP v4.6.5 (Headless, Experience, Commerce) 2024-05-14 To learn more about all the included changes, see the full release change logs: - [Ibexa Headless v4.6.5](https://github.com/ibexa/headless/releases/tag/v4.6.5) - [Ibexa Experience v4.6.5](https://github.com/ibexa/experience/releases/tag/v4.6.5) - [Ibexa Commerce v4.6.5](https://github.com/ibexa/commerce/releases/tag/v4.6.5) To update your application, see the [update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.6/update_from_4.6/#v465). ## Ibexa DXP v4.6.4 (Headless, Experience, Commerce, New feature) 2024-05-13 #### Security This release includes security fixes. To learn more, see the [corresponding security advisory](https://developers.ibexa.co/security-advisories/ibexa-sa-2024-003-vulnerability-in-image-optimizer-dependency). ### Ibexa Engage [Ibexa Engage](https://doc.ibexa.co/en/4.6/ibexa_engage/ibexa_engage/) is a data collection tool you can use to engage your audiences. #### Full changelog To learn more about all the included changes, see the full release change logs: - [Ibexa Headless v4.6.4](https://github.com/ibexa/headless/releases/tag/v4.6.4) - [Ibexa Experience v4.6.4](https://github.com/ibexa/experience/releases/tag/v4.6.4) - [Ibexa Commerce v4.6.4](https://github.com/ibexa/commerce/releases/tag/v4.6.4) To update your application, see the [update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.6/update_from_4.6/#v464). ## Ibexa DXP v4.6.3 (Headless, Experience, Commerce) 2024-04-11 To learn more about all the included changes, see the full release change logs: - [Ibexa Headless v4.6.3](https://github.com/ibexa/headless/releases/tag/v4.6.3) - [Ibexa Experience v4.6.3](https://github.com/ibexa/experience/releases/tag/v4.6.3) - [Ibexa Commerce v4.6.3](https://github.com/ibexa/commerce/releases/tag/v4.6.3) To update your application, see the [update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.6/update_from_4.6/#v463). ## Ibexa DXP v4.6.2 (Headless, Experience, Commerce) 2024-03-20 #### Security This release includes security fixes. To learn more, see the [corresponding security advisory](https://developers.ibexa.co/security-advisories/ibexa-sa-2024-002-file-validation-and-workflow-stages). #### Full changelog To learn more about all the included changes, see the full release change logs: - [Ibexa Headless v4.6.2](https://github.com/ibexa/headless/releases/tag/v4.6.2) - [Ibexa Experience v4.6.2](https://github.com/ibexa/experience/releases/tag/v4.6.2) - [Ibexa Commerce v4.6.2](https://github.com/ibexa/commerce/releases/tag/v4.6.2) To update your application, see the [update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.6/update_from_4.6/#v462). ## Ibexa DXP v4.6.1 (Headless, Experience, Commerce) 2024-02-28 To learn more about all the included changes, see the full release change logs: - [Ibexa Headless v4.6.1](https://github.com/ibexa/headless/releases/tag/v4.6.1) - [Ibexa Experience v4.6.1](https://github.com/ibexa/experience/releases/tag/v4.6.1) - [Ibexa Commerce v4.6.1](https://github.com/ibexa/commerce/releases/tag/v4.6.1) To update your application, see the [update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.6/update_from_4.6/#v461). ## Ibexa DXP v4.6.0 (Headless, Experience, Commerce, New feature, First release) 2024-02-13 ### Notable changes #### Ibexa Headless Ibexa Content changes name to Ibexa Headless to emphasize Ibexa's capacity for headless architecture. The feature set and capabilities of the product remain the same. #### Customizable dashboard (Experience) (Commerce) Users can now customize the dashboard depending on their needs and preferences, select required blocks, and easily access important information. This solution uses an online editor - Dashboard Builder. It improves productivity, allows to enhance the default dashboard with additional widgets, and helps to make better business decisions based on data. *[Image: Customizable dashboard]* For more information, see [Customizable dashboard](https://doc.ibexa.co/projects/userguide/en/master/getting_started/dashboard/dashboard/#customizable-dashboard). #### UX and UI improvements Several improvements to the back office interface enhance the user experience. ##### Page Builder improvements (Experience) (Commerce) Page Builder user interface has new functionalities and improvements. Here are the most important changes: - new design of Page Builder interface, including block settings window, - two main toolboxes: **Elements** and **Structure view**, - quick preview of a structure of the page with the possibility of reorganizing the blocks, - new visual feedback indicates the correct drop locations, - intuitive dragging makes it easier for users to interact with the Page Builder, - new actions added in the block settings toolbox, - user can now adjust the size of the block settings window, - **Undo** and **Redo** buttons. *[Image: Page Builder interface]* For more information, see [Page Builder interface](https://doc.ibexa.co/projects/userguide/en/master/content_management/create_edit_pages/#page-builder-interface). ##### Editing embedded content items User can now edit embedded content items without leaving current window. This function is available in the Rich Text Field when creating content items, for selected blocks in the Page Builder, and while adding or modifying a Content relation. *[Image: Editing embedded content items]* For more information, see [Edit embedded content items](https://doc.ibexa.co/projects/userguide/en/master/content_management/create_edit_content_items/#edit-embedded-content-items). ##### Focus mode With multiple changes to the back office UI intended to expose the most important information and actions, editors can now better focus on their work. The UI is now more friendly and appealing for marketers and editors, with simplified Content structure, designed with new and non-advanced users in mind. For more information, see [Focus mode](https://doc.ibexa.co/projects/userguide/en/latest/getting_started/discover_ui/#focus-mode). *[Image: Focus mode]* As part of this effort, some other changes were introduced that apply to both regular and Focus mode: - In content item details view, tabs have been reordered by their relevance - **Authors** and **Sub-items** are now separate tabs in content item details view - Former **Details** tab is now called **Technical details** and changed its position - Preview is available in many new places, such as the **View** tab in content item details view, or as miniatures when you hover over the content tree - `ibexa_is_focus_mode_on` and `ibexa_is_focus_mode_off` Twig helpers have been introduced, which check whether focus mode is enabled or not. *[Image: Sub-items tab]* ##### Ability to change site context With a drop-down list added to the top bar, which changes the site context, editors can choose that the content tree shows only those content items that belong to the selected website. And if content items belong to multiple websites but use different designs or languages depending on the SiteAccess settings, their previews also change. As part of this effort, the name of the "Sites" area of the main menu has changed to "Site management". *[Image: Site context selector]* ##### Distraction free mode While editing Rich Text Fields, user can switch to distraction free mode. It expands the workspace to full screen and shows only editor toolbar. *[Image: Distraction free mode]* For more information, see [Distraction free mode](https://doc.ibexa.co/projects/userguide/en/master/content_management/create_edit_content_items/#distraction-free-mode). ##### Simplified user actions Button text now precisely describes actions, so that users who create or edit content understand the purpose of each button. *[Image: Improved button text]* ##### Draft section added to Content For streamlining purpose, the **Draft** section is now situated under **Content**. Users can now easily find and manage their drafts and published content from one area. *[Image: Draft section added to Content]* ##### User profile and new options in user settings With personal touch in mind, editors can now upload their photos (avatar), and provide the following information in their user profiles: - Email - Department - Position - Location - Signature - Roles the user is assigned to - Recent activity *[Image: User profile]* Also, editors and other users can customize their experience even better, with new preferences that have been added to user settings. For more information, see [user profile and settings documentation](https://doc.ibexa.co/projects/userguide/en/master/getting_started/get_started/#edit-user-profile). ##### Recent activity log Several actions on the repository or the application are logged. In the back office, last activity logs can be listed on a dedicated interface (Admin -> Activity list), on the dashboard within Recent activity block, or on the user profile. *[Image: Recent activity log]* For more information, see feature's [User Documentation](https://doc.ibexa.co/projects/userguide/en/master/recent_activity/recent_activity/), and [Developer Documentation](https://doc.ibexa.co/en/master/administration/recent_activity/recent_activity/). ##### Back office search ###### Search bar, suggestions, autocompletion, and spellcheck The search bar can be focused with the shortcut Ctrl+/ (Windows, Linux) or Command+/ (Mac). While typing text in the bar, autocompletion suggestions is made under the bar itself. If a relevant suggestion occurs, it can be clicked, or navigated too with up/down keys then selected with Enter, and the content is be directly opened. In the search result page, a spellcheck suggestion can be made. For example, if the searched text is "Comany", the result page may ask "Did you mean company?", which is clickable to relaunch the search with this word. For more information, see [User Documentation](https://doc.ibexa.co/projects/userguide/en/master/search/search_for_content/), and how to [customize autocompletion suggestions](https://doc.ibexa.co/en/master/administration/back_office/customize_search_suggestion/). ###### Filtering and sorting The search result page can be sorted in other orders than relevance. Name, publication of modification dates, this can be extended. Filters can be applied to the search page to narrow down the results. For more information, see [User Documentation](https://doc.ibexa.co/projects/userguide/en/master/search/search_for_content/#filtered-search), and how to [customize search sorting](https://doc.ibexa.co/en/master/administration/back_office/customize_search_sorting/). ##### New and updated content type icons To help users quickly identify different content types in the back office, all content type references are now accompanied with icons. Also, content type icons have changed slightly. *[Image: Content type icons]* #### Ibexa Image picker Editors can now use a Digital Asset Management platform that enables storing media assets in a central location, organizing, distributing, and sharing them across many channels. For more information, see [Ibexa DAM](https://doc.ibexa.co/projects/userguide/en/master/dam/ibexa_dam/). #### New features and improvements in product catalog ##### Remote PIM support This release introduces a foundation for connecting Ibexa DXP's product catalog capabilities to external Product Information Management (PIM) systems. You can use it to implement a custom solution and connect to external PIM or ERP systems, import product data, and present it side-by-side with your organization's existing content, while managing product data in a remote system of your choice. Here are the most important benefits of Remote PIM support: - Integration with external data sources: your organization can utilize Ibexa DXP's features, without having to migrate data to a new environment. - Increased accessibility of product information: customers and users can access product data through different channels, including Ibexa DXP. - Centralized product data management: product information can be maintained and edited in one place, which then serves as a single source of truth for different applications. Among other things, the Remote PIM support feature allows Ibexa DXP customers to: - let their users purchase products by following a regular or quick order path, - manage certain aspects of product data, - define and use product types, - use product attributes for filtering, - build product catalogs based on certain criteria, such as type, availability, or product attributes, - use Customer Groups to apply different prices to products, - define and use currencies. For more information about Remote PIM support and the solution's limitations, see [Product catalog](https://doc.ibexa.co/en/5.0/product_catalog/product_catalog_guide/#limitations). ##### Virtual products With this feature, you can create virtual products - non-tangible items such as memberships, services, warranties. Default Checkout and Order workflows have been adjusted to allow purchase of virtual products. For more information, see [Create virtual products](https://doc.ibexa.co/projects/userguide/en/master/pim/create_virtual_product/). ##### Product page URLs When you're creating a new product type, you can set up a product URL alias name pattern. With this feature, you can also create custom URL and URL alias name pattern field based on product attributes. Customized URLs are easier to remember, help with SEO optimization and reduce bounce rates on the website. For more information, see [Product page URLs](https://doc.ibexa.co/projects/userguide/en/master/pim/work_with_product_page_urls/). ##### Improved UX of VAT rate assignment Users who are creating or editing a product type are less likely to forget about setting VAT rates, because they now have a more prominent place. *[Image: Assigning VAT rates to a product type]* For more information, see [Create product types](https://doc.ibexa.co/projects/userguide/en/master/pim/create_product_types/). ##### Updated VAT configuration VAT rates configuration has been extended to accept additional flags under the `extras` key. Developers can use them, for example, to pass additional information to the UI, or define special exclusion rules. For more information, see [VAT rates](https://doc.ibexa.co/en/master/pim/pim_configuration/#vat-rates). ##### Ability to search through products in a catalog When you're reviewing catalog details, on the **Products** tab, you can now see what criteria are used to include products in the catalog, and search for a specific product in the catalog. ##### New Twig functions The `ibexa_is_pim_local` Twig helper has been introduced, which can be used in templates to [check whether product data comes from a local or remote data source](https://doc.ibexa.co/en/master/templating/twig_function_reference/storefront_twig_functions/#ibexa_is_pim_local), and adjust their behavior accordingly. Also, several new Twig functions have been implemented that help [get product availability information](https://doc.ibexa.co/en/master/templating/twig_function_reference/product_twig_functions/#ibexa_has_product_availability). ##### New and modified query types The `ProductContentAwareListQueryType` has been created to allow finding products that come from a local database, while `ProductListQueryType` has been modified to find products from an external source of truth. ##### New Search Criterion With `IsVirtual` criterion that searches for virtual or physical products, product search now supports products of virtual and physical type. ##### Product migration [Product variants](https://doc.ibexa.co/en/master/content_management/data_migration/importing_data/#product-variants) and [product assets](https://doc.ibexa.co/en/master/content_management/data_migration/importing_data/#product-assets) can now be created through [data migration](https://doc.ibexa.co/en/master/content_management/data_migration/data_migration/). #### New features and improvements in Commerce (Commerce) ##### Reorder With the new Reorder feature, customers can effortlessly repurchase previously bought items directly from their order history with a single click, eliminating the need for manual item selection. The system streamlines the process by recreating the cart, retrieving shipping information, and pre-filling payment details from past orders. This feature is exclusively accessible to logged-in users, ensuring a secure and personalized shopping experience. For more information, see [reorder documentation](https://doc.ibexa.co/en/master/commerce/checkout/reorder/). ##### Orders block Orders block displays a list of orders associated with a specific company or an individual customer. This block allows users to configure orders statuses, columns, number of orders, and sorting order. For more information, see [Orders block documentation](https://doc.ibexa.co/projects/userguide/en/master/content_management/block_reference/#orders-block). ##### Quick order The quick order form allows users to streamline the process of placing orders with multiple items in bulk directly from the storefront. Customers don't need to browse through products in the catalog. They can fill in a provided form with products' code and quantity, or upload their own list directly into the system. Quick order form is available to both registered and guest users. *[Image: Quick order]* For more information, see [Quick order documentation](https://doc.ibexa.co/en/master/commerce/cart/quick_order/). ##### Cancel order This version allows you to customize order cancellations by defining a specific order status and related transition. For more information, see [Define cancel order](https://doc.ibexa.co/en/master/commerce/order_management/configure_order_management/#define-cancel-order). ##### Integrate with payment gateways Ibexa DXP can now be configured to integrate with various payment gateways, like Stripe and PayPal, by using the solution provided by [Payum](https://github.com/Payum). ##### Shipments Users can now work with the shipments: view and modify their status, filter shipments in shipment lists and check all the details. You can access shipments for your own orders or all the shipments that exist in the system, depending on your permissions. *[Image: Shipments]* For more information, see [Work with shipments](https://doc.ibexa.co/projects/userguide/en/master/commerce/shipping_management/work_with_shipments/). ##### Owner criterion Orders and shipments search now supports user reference: - `OwnerCriterion` Criterion searches for orders based on the user reference. - `Owner` Criterion searches for shipments based on the user reference. ##### Customize checkout workflow You can create a PHP definition of the new strategy that allows for workflow manipulation. Defining strategy allows to add conditional steps for workflow if needed. When a conditional step is added, the checkout process uses the specified workflow and proceeds to the defined step. For more information, see [Create custom strategy](https://doc.ibexa.co/en/master/commerce/checkout/customize_checkout/#create-custom-strategy). ##### Manage multiple checkout workflows When working with multiple checkout workflows, you can now specify the desired workflow by passing its name as an argument to the checkout initiation button or link. For more information, see [Manage multiple workflows](https://doc.ibexa.co/en/master/commerce/checkout/customize_checkout/#manage-multiple-workflows). ##### Adding context data to cart Attach context data to both the Cart and its individual Cart Entries. This feature enhances the flexibility and customization of your e-commerce application, enabling you to associate additional information with your cart and its contents. By leveraging context data, such as promo codes or custom texts, you can tailor the shopping experience for your customers and enhance the capabilities of your application. For more information, see [Adding context data](https://doc.ibexa.co/en/master/commerce/cart/cart_api/#adding-context-data). #### New features and improvements in Personalization ##### Triggers Triggers are push messages delivered to end users. With triggers, store managers can increase the engagement of their visitors and customers by delivering recommendations straight to their devices or mailboxes. While they experience improved fulfillment of their needs, more engaged customers mean bigger income for the store. The feature requires that your organization exposes an endpoint that passes data to an internal message delivery system and supports the following use cases: - Inducing a purchase by pushing a message with cart contents or equivalents, when the customer's cart status remains unchanged for a set time. - Inviting a customer to come back to the site by pushing a message with recommendations, when they haven't returned to the site for a set time. - Reviving the customer's interest by pushing a message with products that are similar to the ones the customer has already seen. - Inducing a purchase by pushing a message when a price of the product from the customer's wishlist decreases. For more information, see [Email triggers](https://doc.ibexa.co/projects/userguide/en/master/personalization/triggers/). ##### Multiple attributes in recommendation computation With this feature, you get an option to combine several attribute types when computing recommendations. As a result, users can be presented with recommendations from an intersection of submodel results. For more information, see [Submodel parameters](https://doc.ibexa.co/en/master/personalization/api_reference/recommendation_api/#submodel-parameters) and [Submodels](https://doc.ibexa.co/projects/userguide/en/latest/personalization/recommendation_models/#submodels). ##### New scenario filter Depending on a setting that you make when defining a scenario, the recommendation response can now include either product variants or base products only. This way you can deliver more accurate recommendations and avoid showing multiple variants of the same product to the client. For more information, see [Commerce-specific filters](https://doc.ibexa.co/projects/userguide/en/latest/personalization/filters/#commerce-specific-filters). ### Other changes #### Expression Language New `project_dir()` expression language function that allows you to reference current project directory in YAML migration files. #### Site Factory events Site Factory events have been moved from the `Ibexa\SiteFactory\ServiceEvent\Events` namespace to the `Ibexa\Contracts\SiteFactory\Events` namespace, keeping the backward compatibility. For a full list of events, see [Site events](https://doc.ibexa.co/en/latest/api/event_reference/site_events/). Event handling system was improved with the addition of listeners based on `CreateSiteEvent`, `DeleteSiteEvent`, and `UpdateSiteEvent`. New listeners automatically grant permissions to log in to a site, providing a more seamless site management experience. #### Integration with Actito By using the Actito gateway you can send emails to the end-users about changes in the status of various operations in your commerce presence. #### Integration with Qualifio Engage Use Qualifio Engage integration to create engaging marketing experiences to your customers. #### Integration with SeenThis! Unlike conventional streaming services, integration with SeenThis! service provides an adaptive streaming technology with no limitations. It allows you to preserve the best video quality with a minimum amount of data transfer. For more information, see [SeenThis! block](https://doc.ibexa.co/projects/userguide/en/master/content_management/block_reference/#seenthis-block). #### API improvements ##### REST API ###### REST API for shipping (Commerce) Endpoints that allow you to manage shipping methods and shipments by using REST API: - GET `/shipments` - loads a list of shipments - GET `/shipments/{identifier}` - loads a single shipment based on its identifier - PATCH `/shipments/{identifier}` - updates a shipment - GET `/shipping/methods` - loads shipping methods - GET `/shipping/methods/{identifier}` - loads shipping methods based on their identifiers - GET `/shipping/method-types` - loads shipping methods types - GET `/shipping/method-types/{identifier}` - loads shipping methods type based on their identifiers - GET `/orders/order/{identifier}/shipments` - loads a list of shipments ###### REST API for company accounts (Experience) (Commerce) Endpoints that allow you to manage companies in your platform with REST API: - GET `/sales-representatives` - returns paginated list of available sales representatives ###### REST API for prices Endpoints that allow you to manage prices in your platform with REST API: - GET `/product/catalog/products/{code}/prices` - loads a list of product prices - GET `/product/catalog/products/{code}/prices/{currencyCode}` - loads a list of product prices for a given currency - GET `/product/catalog/products/{code}/prices/{currencyCode}/customer-group/{identifier}` - loads a list of product prices for a given currency and customer group - POST `/product/catalog/products/{code}/prices` - creates price or custom price for a given product - PATCH `/product/catalog/products/{code}/prices/{id}` - updates price or custom price for a given product - DELETE `/product/catalog/products/{code}/prices/{id}` - deletes price for a given product ###### New method signature A signature for the `\Ibexa\Contracts\Rest\Output\Generator::startValueElement` method has been updated to the following: ``` /** * @phpstan-param scalar $value * @phpstan-param array $attributes */ abstract public function startValueElement(string $name, $value, array $attributes = []): void; ``` Any third party code that extends `\Ibexa\Contracts\Rest\Output\Generator` needs to update the method signature accordingly. #### Helpers A new helper method `ibexa.helpers.contentType.getContentTypeDataByHref` has been introduced to help you get content type data in JavaScript. #### Ibexa Connect For a list of changes in Ibexa Connect, see [Ibexa app release notes](https://doc.ibexa.co/projects/connect/en/latest/general/ibexa_app_release_notes/). ##### Scenario block New Ibexa Connect scenario block retrieves and displays data from an Ibexa Connect webhook. Scenario block is a regular Page block and can be configured on field definition level as any other block. You also need to configure scenario block in the Page Builder. To do it, you need to provide name for the block, enter webhook link for the Ibexa Connect webhook and select the template to be used to present the webhook. For more information, see [Ibexa Connect scenario block](https://doc.ibexa.co/en/master/content_management/pages/ibexa_connect_scenario_block/). #### DDEV [Ibexa DXP can officially be run on DDEV](https://ddev.readthedocs.io/en/latest/users/quickstart/#ibexa-dxp). For more information, see the [DDEV guide](https://doc.ibexa.co/en/master/getting_started/install_with_ddev/), which offers a step-by-step walkthrough for installing Ibexa DXP. #### Customer Data Platform (CDP) In this release, the CDP configuration allows you to automate the process of exporting data. Users can now export not only Content, but also Users and Products data. For more information, see [CDP Activation](https://doc.ibexa.co/en/master/cdp/cdp_activation/cdp_activation/). ### Developer experience #### New packages The following packages have been introduced in Ibexa DXP v4.6.0: - [ibexa/oauth2-server](https://doc.ibexa.co/en/4.6/users/oauth_server/) (optional) - ibexa/site-context - ibexa/activity-log - ibexa/notifications - ibexa/dashboard - ibexa/connector-seenthis (optional) - ibexa/connector-actito (optional) - ibexa/connector-qualifio (optional) - ibexa/connector-payum - ibexa/image-picker - ibexa/core-persistence - ibexa/corporate-account-commerce-bridge > **Note: Note** > > The ibexa/content package has been renamed to ibexa/headless. #### REST APIs Ibexa DXP v4.6.0 adds REST API coverage for the following features: - Price engine - Shipping - Corporate accounts - Activity Log - UDW configuration (internal) ##### Endpoints list The following endpoints have been added in 4.6.0 release (27 endpoints in total): | Endpoint | Functions | | | Parameters | | ------------------------------------------------------------------------ | --------- | --- | --- | --------------------------------------------------------------------------------------------------------------------- | | `ibexa.activity_log.rest.activity_log.list` | GET/POST | ANY | ANY | `/api/ibexa/v2/activity-log/list` | | `ibexa.udw.location.data` | GET | ANY | ANY | `/api/ibexa/v2/module/universal-discovery/location/{locationId}` | | `ibexa.udw.location.gridview.data` | GET | ANY | ANY | `/api/ibexa/v2/module/universal-discovery/location/{locationId}/gridview` | | `ibexa.udw.locations.data` | GET | ANY | ANY | `/api/ibexa/v2/module/universal-discovery/locations` | | `ibexa.udw.accordion.data` | GET | ANY | ANY | `/api/ibexa/v2/module/universal-discovery/accordion/{locationId}` | | `ibexa.udw.accordion.gridview.data` | GET | ANY | ANY | `/api/ibexa/v2/module/universal-discovery/accordion/{locationId}/gridview` | | `ibexa.rest.application_config` | GET | ANY | ANY | `/api/ibexa/v2/application-config` | | `ibexa.cart.authorize` | POST | ANY | ANY | `/api/ibexa/v2/cart/authorize` | | `ibexa.rest.corporate_account.sales_representatives.get` | GET | ANY | ANY | `/api/ibexa/v2/corporate/sales-representatives` | | `ibexa.product_catalog.rest.prices.create` | POST | ANY | ANY | `/api/ibexa/v2/product/catalog/products/{productCode}/prices` | | `ibexa.product_catalog.rest.prices.list` | GET | ANY | ANY | `/api/ibexa/v2/product/catalog/products/{productCode}/prices` | | `ibexa.product_catalog.rest.prices.get.custom_price` | GET | ANY | ANY | `/api/ibexa/v2/product/catalog/products/{productCode}/prices/{currencyCode}/customer-group/{customerGroupIdentifier}` | | `ibexa.product_catalog.rest.prices.get.base_price` | GET | ANY | ANY | `/api/ibexa/v2/product/catalog/products/{productCode}/prices/{currencyCode}` | | `ibexa.product_catalog.rest.prices.update` | PATCH | ANY | ANY | `/api/ibexa/v2/product/catalog/products/{productCode}/prices/{id}` | | `ibexa.product_catalog.rest.prices.delete` | DELETE | ANY | ANY | `/api/ibexa/v2/product/catalog/products/{productCode}/prices/{id}` | | `ibexa.product_catalog.personalization.rest.product_variant.get_by_code` | GET | ANY | ANY | `/api/ibexa/v2/personalization/v1/product_variant/code/{code}` | | `ibexa.product_catalog.personalization.rest.product_variant_list` | GET | ANY | ANY | `/api/ibexa/v2/personalization/v1/product_variant/list/{codes}` | | `ibexa.shipping.rest.shipping_method.type.list` | GET | ANY | ANY | `/api/ibexa/v2/shipping/method-types` | | `ibexa.shipping.rest.shipping_method.type.get` | GET | ANY | ANY | `/api/ibexa/v2/shipping/method-types/{identifier}` | | `ibexa.shipping.rest.shipping_method.get` | GET | ANY | ANY | `/api/ibexa/v2/shipping/methods/{identifier}` | | `ibexa.shipping.rest.shipping_method.find` | GET | ANY | ANY | `/api/ibexa/v2/shipping/methods` | | `ibexa.shipping.rest.shipment.get` | GET | ANY | ANY | `/api/ibexa/v2/shipments/{shipmentIdentifier}` | | `ibexa.shipping.rest.shipment.delete` | DELETE | ANY | ANY | `/api/ibexa/v2/shipments/{shipmentIdentifier}` | | `ibexa.shipping.rest.shipment.all.find` | GET | ANY | ANY | `/api/ibexa/v2/shipments` | | `ibexa.shipping.rest.shipment.order.find` | GET | ANY | ANY | `/api/ibexa/v2/orders/order/{orderIdentifier}/shipments` | | `ibexa.shipping.rest.shipment.create` | POST | ANY | ANY | `/api/ibexa/v2/orders/order/{orderIdentifier}/shipments` | | `ibexa.shipping.rest.shipment.update` | PATCH | ANY | ANY | `/api/ibexa/v2/shipments/{shipmentIdentifier}` | #### PHP API - Autosave API (`\Ibexa\Contracts\AdminUi\Autosave\AutosaveServiceInterface`) - Activity Log API - Spellchecking API - Site Context API (`\Ibexa\Contracts\SiteContext\SiteContextServiceInterface`) - Dashboard API (`\Ibexa\Contracts\Dashboard\DashboardServiceInterface`) - Price resolver API (`\Ibexa\Contracts\ProductCatalog\PriceResolverInterface`) - Location Preview URL resolver (`\Ibexa\Contracts\SiteContext\PreviewUrlResolver\LocationPreviewUrlResolverInterface`, see [GitHub](https://github.com/ibexa/site-context/pull/25)) - ContentAware API (`\Ibexa\Contracts\Core\Repository\Values\Content\ContentAwareInterface`) - Sorting Definition API (`\Ibexa\Contracts\Search\SortingDefinition`) #### Search Criteria Content - `\Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion\ContentName` - Image criteria: - `\Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion\Image\Dimensions` - `\Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion\Image\FileSize` - `\Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion\Image\Height` - `\Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion\Image\MimeType` - `\Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion\Image\Orientation` - `\Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion\Image\Width` Product - `\Ibexa\Contracts\ProductCatalog\Values\Product\Query\Criterion\IsVirtual` - `ProductStock` and `ProductStockRange` #### Sort Clauses - `\Ibexa\Contracts\ProductCatalog\Values\Product\Query\SortClause\ProductStock` #### Aggregations - Aggregation API for product catalog - Labeled ranges - Range::INF to improve readability of unbounded ranges - Added support for creating range aggregations from generator (see `\Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\Ranges\RangesGeneratorInterface`) and built-in step generators: - `\Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\Ranges\DateTimeStepRangesGenerator` - `\Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\Ranges\FloatStepRangesGenerator` - `\Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\Ranges\IntegerStepRangesGenerator` - Allowed direct access to aggregation keys from results - `\Ibexa\Contracts\Core\Repository\Values\Content\Search\AggregationResult\TermAggregationResult::getKeys` - `\Ibexa\Contracts\Core\Repository\Values\Content\Search\AggregationResult\RangeAggregationResult::getKeys` #### Events The following events have been added in the v4.6.0 release (39 events in total): - ibexa/activity-log - `\Ibexa\Contracts\ActivityLog\Event\PostActivityListLoadEvent` - ibexa/admin-ui - `\Ibexa\Contracts\AdminUi\Event\FocusModeChangedEvent` - ibexa/cart - `\Ibexa\Contracts\AdminUi\Event\FocusModeChangedEvent` - `\Ibexa\Contracts\Cart\Event\BeforeMergeCartsEvent` - ibexa/core - URL and name schema resolving events: - `\Ibexa\Contracts\Core\Event\NameSchema\ResolveUrlAliasSchemaEvent` - `\Ibexa\Contracts\Core\Event\NameSchema\ResolveNameSchemaEvent` - `\Ibexa\Contracts\Core\Event\NameSchema\ResolveContentNameSchemaEvent` - Tokens - `\Ibexa\Contracts\Core\Repository\Events\Token\BeforeRevokeTokenByIdentifierEvent` - `\Ibexa\Contracts\Core\Repository\Events\Token\BeforeRevokeTokenEvent` - `\Ibexa\Contracts\Core\Repository\Events\Token\RevokeTokenByIdentifierEvent` - `\Ibexa\Contracts\Core\Repository\Events\Token\RevokeTokenEvent` - ibexa/migration - `\Ibexa\Contracts\Migration\Event\BeforeMigrationEvent` - `\Ibexa\Contracts\Migration\Event\MigrationEvent` - ibexa/page-builder - `\Ibexa\Contracts\PageBuilder\Event\GenerateContentPreviewUrlEvent` - ibexa/search: - `\Ibexa\Contracts\Search\Event\Service\BeforeSuggestEvent` - `\Ibexa\Contracts\Search\Event\Service\SuggestEvent` - ibexa/segmentation - `\Ibexa\Contracts\Segmentation\Event\AssignUserToSegmentEvent` - `\Ibexa\Contracts\Segmentation\Event\BeforeAssignUserToSegmentEvent` - `\Ibexa\Contracts\Segmentation\Event\BeforeCreateSegmentEvent` - `\Ibexa\Contracts\Segmentation\Event\BeforeCreateSegmentGroupEvent` - `\Ibexa\Contracts\Segmentation\Event\BeforeRemoveSegmentEvent` - `\Ibexa\Contracts\Segmentation\Event\BeforeRemoveSegmentGroupEvent` - `\Ibexa\Contracts\Segmentation\Event\BeforeUnassignUserFromSegmentEvent` - `\Ibexa\Contracts\Segmentation\Event\BeforeUpdateSegmentEvent` - `\Ibexa\Contracts\Segmentation\Event\BeforeUpdateSegmentGroupEvent` - `\Ibexa\Contracts\Segmentation\Event\CreateSegmentEvent` - `\Ibexa\Contracts\Segmentation\Event\CreateSegmentGroupEvent` - `\Ibexa\Contracts\Segmentation\Event\RemoveSegmentEvent` - `\Ibexa\Contracts\Segmentation\Event\RemoveSegmentGroupEvent` - `\Ibexa\Contracts\Segmentation\Event\UnassignUserFromSegmentEvent` - `\Ibexa\Contracts\Segmentation\Event\UpdateSegmentEvent` - `\Ibexa\Contracts\Segmentation\Event\UpdateSegmentGroupEvent` - ibexa/site-context - `\Ibexa\Contracts\SiteContext\Event\ResolveLocationPreviewUrlEvent` - ibexa/site-factory - `\Ibexa\Contracts\SiteFactory\Events\BeforeCreateSiteEvent` - `\Ibexa\Contracts\SiteFactory\Events\BeforeDeleteSiteEvent` - `\Ibexa\Contracts\SiteFactory\Events\BeforeUpdateSiteEvent` - `\Ibexa\Contracts\SiteFactory\Events\CreateSiteEvent` - `\Ibexa\Contracts\SiteFactory\Events\DeleteSiteEvent` - `\Ibexa\Contracts\SiteFactory\Events\UpdateSiteEvent` #### Twig functions - `ibexa_is_user_profile_available` - `ibexa_is_focus_mode_on` - `ibexa_is_focus_mode_off` - `ibexa_is_pim_local` - `ibexa_current_user` - `ibexa_is_current_user` - `ibexa_get_user_preference_value` - `ibexa_has_user_preference` - `ibexa_has_field` - `ibexa_field_group_name` - `ibexa_render_activity_log` - `ibexa_render_activity_log_group` - `ibexa_choices_as_facets` - `ibexa_taxonomy_entries_for_content` - `ibexa_url` / `ibexa_path` (support for content wrappers) #### View matchers The following view matchers have been introduced in Ibexa DXP v4.6.0: - `\Ibexa\Core\MVC\Symfony\Matcher\ContentBased\IsPreview` - `\Ibexa\Taxonomy\View\Matcher\TaxonomyEntryBased\Id` - `\Ibexa\Taxonomy\View\Matcher\TaxonomyEntryBased\Identifier` - `\Ibexa\Taxonomy\View\Matcher\TaxonomyEntryBased\Level` - `\Ibexa\Taxonomy\View\Matcher\TaxonomyEntryBased\Taxonomy` ### Full changelog To learn more about all the included changes, see the full release change logs: - [Ibexa Headless v4.6.0](https://github.com/ibexa/headless/releases/tag/v4.6.0) - [Ibexa Experience v4.6.0](https://github.com/ibexa/experience/releases/tag/v4.6.0) - [Ibexa Commerce v4.6.0](https://github.com/ibexa/commerce/releases/tag/v4.6.0) To update your application, see the [update instructions](https://doc.ibexa.co/en/4.6/update_and_migration/from_4.6/update_from_4.6/). # Ibexa DXP v4.5 **Version number**: v4.5 **Release date**: May 12, 2023 **Release type**: [Fast Track](https://support.ibexa.co/Public/service-life) **Update**: [v4.4.x to v4.5](https://doc.ibexa.co/en/latest/update_and_migration/from_4.4/update_from_4.4/) ## Notable changes ### All-new Ibexa Commerce packages (Commerce) This release brings new packages to complement the redesigned and reconstructed Commerce offering. You can use them to further enhance your e-commerce presence: - `ibexa/order-management` - `ibexa/payment` - `ibexa/shipping` Modules can interact with each other, for example, to decrease stock as a result of a sale, or cancel shipments and payments when orders are cancelled. #### Order management With order management in place, it's now possible to create orders, configure and customize the order processing workflow, and manage orders by using the APIs. New screens added to the back office user interface let Ibexa DXP users search for orders and filter search results. Users can also review order details and completion status, and cancel orders. *[Image: The order list screen]* #### Payment The all-new Payment module brings a possibility of tracking payment progress and defining a custom payment processing workflow. New back office screens allow users to search for payment methods and payments, and also define, enable, and disable offline payment methods. Additionally, new APIs are available, which can be used for managing payment methods and payments. *[Image: The payment methods screen]* #### Shipping With the arrival of the Shipping module, it's now possible to define and manage shipping methods of different types, together with their related costs, on a dedicated back office screen. You can now also configure and customize the shipment workflow. New APIs enable managing shipping methods and payments, while an extension point can be used to expand the default list of shipping method types. *[Image: The shipping methods screen]* For more information, see [Commerce](https://doc.ibexa.co/en/4.5/commerce/commerce/). ### New commerce page blocks (Experience) (Commerce) This release introduces new page blocks: - [Bestsellers block](https://doc.ibexa.co/projects/userguide/en/4.5/content_management/block_reference/#bestsellers-block) displays a list of products from the product catalog that were recently a bestseller. *[Image: Bestsellers block]* - [React app block](https://doc.ibexa.co/en/4.5/content_management/pages/react_app_block/) allows an editor to embed a preconfigured React application in a page. React app block requires configuration. For more information, see [React App Block configuration](https://doc.ibexa.co/en/4.5/content_management/pages/react_app_block/#react-app-block-configuration). *[Image: React app block]* ### Translation comparison With this release, you can compare different versions of translations of a content item, including comparison between different languages. You can now choose between two new options of the view: - Split - default, side by side view to compare versions of the same or different languages - Unified - single column view to compare versions of the same language Now, when you compare different versions within the same language, the system highlights the changes using colors: - yellow - content updated - blue - content added - red - content deleted *[Image: Translation comparison]* For more information, see [Translation comparison](https://doc.ibexa.co/projects/userguide/en/4.5/content_management/translate_content/#translation-comparison). ### Page Builder for B2B portals (Experience) (Commerce) With this release, you're able to use Page Builder to create custom Customer Portals for your clients. With new Sales rep page block and using all available blocks from the original Page Builder, you can create a unique experience for each customer group. Additionally, you can assign each customer group to a specific Customer Portal or create an availability hierarchy based on rules and configuration. *[Image: Page Builder for B2B portals]* For more information, see [backend configuration](https://doc.ibexa.co/en/4.5/customer_management/cp_page_builder/) and [user guide](https://doc.ibexa.co/projects/userguide/en/4.5/customer_management/build_customer_portal/) on how to create and edit Customer Portals. ### Personalization improvements #### New B2B models in Personalization engine Personalization engine introduces two new types of models: [last clicked and last purchased B2B, and B2B recurring purchase models](https://doc.ibexa.co/projects/userguide/en/4.5/personalization/recommendation_models/#b2b-model), dedicated to B2B users. Built on the fly, and based on segment groups, the models return actual items clicked by users with the same segment ID and actual bought items. B2B recurring purchase model anticipates and predicts purchase of products that were bought recursively within the same segment ID. ### Segment management Now you can use segmentation logic with operators to build complex segment groups which enable precise filtering. With intuitive drag-and-drop interface, define rules, add logic operators and nest segments in segment groups to get the most accurate, precise and targeted recommendations for your customers. *[Image: Segment management]* For more information, see [Segment management](https://doc.ibexa.co/projects/userguide/en/4.5/personalization/segment_management/). ## Other changes ### Customer Data Platform (CDP) configuration In this release, the CDP configuration becomes more generic and allows supporting other transport types accepted by CDP. Currently, only `stream_file` transport is supported and can be initialized from the configuration. Ibexa DXP v4.5 adds the abstraction that allows you to implement other transport types from third parties. For more information, see [CDP configuration](https://doc.ibexa.co/en/4.5/cdp/cdp_activation/#configuration). ### API improvements #### REST API for company accounts (Experience) (Commerce) This release adds new endpoints that allow you to manage companies in your platform with REST API: - GET `/corporate/companies` - supports pagination and existing Content Criteria and Sort Clauses but via query parameters - POST `/corporate/companies` - creates a company - GET `/corporate/companies/{companyId}` - loads a company - DELETE `/corporate/companies/{companyId}` - deletes a company - PATCH `/corporate/companies/{companyId}` - updates company data - GET `/corporate/companies/{companyId}/members` - supports filtering, sorting, and pagination - POST `/corporate/companies/{companyId}/members` - creates new member in a company - GET `/corporate/companies/{companyId}/members/{memberId}` - loads a member from a company - DELETE `/corporate/companies/{companyId}/members/{memberId}` - deletes a member from a company - PATCH `/corporate/companies/{companyId}/members/{memberId}` - updates member data #### PHP API for company accounts (Experience) (Commerce) To create a company with proper structure and shipping address by using PHP API, we recommend new `\Ibexa\Contracts\CorporateAccount\Service\CorporateAccountService::createCompany` service instead of `\Ibexa\Contracts\CorporateAccount\Service\CompanyService::createCompany`. #### REST API for order management (Commerce) This release adds new endpoints that allow you to manage orders by using REST API: - GET `/orders/orders` - loads a list of orders - POST `/orders/orders` - creates an order - GET `/orders/order` - loads an order by its identifier - GET `/orders/order/{id}` - loads an order - POST `/orders/orders/{id}` - cancels an order - PATCH `/orders/orders/{id}` - updates an order #### PHP API for order management (Commerce) The Order Management package provides the `Ibexa\Contracts\OrderManagement\OrderServiceInterface` service, which is the entrypoint for calling the backend API for managing orders. #### PHP API for shipping methods and shipments (Commerce) The Checkout package provides the following services that are entrypoints to the backend API: - `Ibexa\Contracts\Shipping\ShipmentServiceInterface` for managing shipments - `Ibexa\Contracts\Shipping\ShippingMethodServiceInterface` for managing shipment methods #### PHP API for payment methods and payments (Commerce) The Payment package provides the following services that are entrypoints to the backend API: - `Ibexa\Contracts\Payment\PaymentServiceInterface` for managing payments - `Ibexa\Contracts\Payment\PaymentMethodServiceInterface` for managing payment methods ### Category filter in product search To help users search for products, products in the main catalog view can now be filtered by product category. *[Image: Product categories filter]* ### Product aggregations Product search now supports aggregations, with the following aggregations available: - [Product attribute](https://doc.ibexa.co/en/4.5/search/aggregation_reference/product_attribute_aggregations/) - based on product attribute values - [ProductAvailabilityTerm](https://doc.ibexa.co/en/4.5/search/aggregation_reference/productavailabilityterm_aggregation/) - based on product availability - [ProductPriceRange](https://doc.ibexa.co/en/4.5/search/aggregation_reference/productpricerange_aggregation/) - based on product price - [ProductTypeTerm](https://doc.ibexa.co/en/4.5/search/aggregation_reference/producttypeterm_aggregation/) - based on product type The new [TaxonomyEntryIdAggregation](https://doc.ibexa.co/en/4.5/search/aggregation_reference/taxonomyentryid_aggregation/) aggregates results based on content taxonomy entries or product categories. ### Tag identifiers The taxonomy entry identifier uniqueness has been changed from globally unique to unique per taxonomy. It's no longer necessary to take other taxonomies into account when creating tags in a taxonomy. ### Password security You can now enhance password security with a setting that prevents using passwords that have been exposed in a public breach. To do it, the system checks the password against known password dumps by using the API. For more information, see [Breached passwords](https://doc.ibexa.co/en/4.5/users/user_management/#breached-passwords). ### Ibexa Connect For list of changes in Ibexa Connect, see [Ibexa app release notes](https://doc.ibexa.co/projects/connect/en/latest/general/ibexa_app_release_notes/). ### Deprecations #### `ibexa/admin-ui` Changes: - `\Ibexa\PageBuilder\Siteaccess\SiteaccessService::resolveSiteAccessForContent` moved to `\Ibexa\AdminUi\Siteaccess\SiteaccessResolverInterface` Deprecations: - `\Ibexa\AdminUi\Siteaccess\SiteaccessResolverInterface::getSiteaccessesForLocation` replaced by `\Ibexa\AdminUi\Siteaccess\SiteaccessResolverInterface::getSiteAccessesList` - `\Ibexa\AdminUi\Siteaccess\SiteaccessResolverInterface::getSiteaccesses` replaced by `\Ibexa\AdminUi\Siteaccess\SiteaccessResolverInterface::getSiteAccessesListForLocation` ## Full changelog | Ibexa Content | Ibexa Experience | Ibexa Commerce | | -------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | | [Ibexa Content v4.5](https://github.com/ibexa/content/releases/tag/v4.5.0) | [Ibexa Experience v4.5](https://github.com/ibexa/experience/releases/tag/v4.5.0) | [Ibexa Commerce v4.5](https://github.com/ibexa/commerce/releases/tag/v4.5.0) | ## v4.5.1 ### Product category tree filter In the main catalog view, the tree of categories now has a search input to reduce the tree to matching categories. *[Image: Product category tree filter]* ### Product stock criteria and aggregation Product search now supports stock availability: - [ProductStock Criterion](https://doc.ibexa.co/en/4.5/search/criteria_reference/productstock_criterion/) - searches for products with a stock compared to a given number - [ProductStockRange Criterion](https://doc.ibexa.co/en/4.5/search/criteria_reference/productstockrange_criterion/) - searches for products with a stock in a given range - [ProductStockRangeAggregation](https://doc.ibexa.co/en/4.5/search/aggregation_reference/productstockrange_aggregation/) - aggregates search results by products' stock ranges ### `X-Expected-User` REST request header The [`X-Expected-User` header](https://doc.ibexa.co/en/4.5/api/rest_api/rest_api_usage/rest_requests/#expected-user) checks that the REST request is executed with the desired user (and not, for example, the Anonymous user because of an expired authentication). # Ibexa DXP v4.4 **Version number**: v4.4 **Release date**: February 2, 2023 **Release type**: [Fast Track](https://support.ibexa.co/Public/service-life) **Update**: [v4.3.x to v4.4](https://doc.ibexa.co/en/latest/update_and_migration/from_4.3/update_from_4.3/) ## Notable changes ### New welcome page A new welcome page greets you when opening Ibexa Digital Experience Platform. *[Image: New Welcome Page]* ### All-new Ibexa Commerce packages (Commerce) This release deprecates all Commerce packages that you've known from previous releases and brings a redesigned and reconstructed Commerce offering: - `ibexa/cart` - `ibexa/checkout` - `ibexa/storefront` As part of this effort, two all-new components have been created: Cart and Checkout, that you can use to build your own e-commerce presence. *[Image: The new cart view]* *[Image: The new checkout]* For more information, see [Commerce](https://doc.ibexa.co/en/4.4/commerce/commerce/). #### Storefront Another addition is the Storefront package that provides a starting kit for the developers. It's a working set of components, which you can use to test the new capabilities, and then customize and extend to create your own implementation of a web store. For more information, see [Storefront](https://doc.ibexa.co/en/4.4/commerce/storefront/storefront). ### Fastly Image Optimizer (Fastly IO) You can now use Fastly IO to serve optimized versions of your images in real time and cache them. Fastly can perform multiple transformations on your image, for example, cropping, resizing, and trimming before serving it to end user. Fastly is an external service that requires a separate subscription, to learn more see, [Fastly Image Optimizer website](https://docs.fastly.com/en/guides/about-fastly-image-optimizer). If you already have Fastly IO subscription, you can move to [Fastly IO configuration in Ibexa DXP](https://doc.ibexa.co/en/4.4/content_management/images/fastly_io/). #### Fastly VCL upload With this release, you can manipulate your Fastly VCL configuration directly from the command line. For example, you can define formats or source path for images. ### New page blocks (Experience) (Commerce) This release introduces new page blocks that rely on Personalization and product catalog features to let editors visually organize products on a page: - [Catalog block](https://doc.ibexa.co/projects/userguide/en/4.4/content_management/block_reference/#catalog-block) displays products from a specific catalog to a selected customer group. - [Last purchased](https://doc.ibexa.co/projects/userguide/en/4.4/content_management/block_reference/#last-purchased-block) displays a list of products that were recently purchased, either generally, or by a specific user. - [Last viewed](https://doc.ibexa.co/projects/userguide/en/4.4/content_management/block_reference/#last-viewed-block) displays a list of products that were recently viewed. - [Product collection](https://doc.ibexa.co/projects/userguide/en/4.4/content_management/block_reference/#product-collection-block) displays a collection of specifically selected products. - [Recently added](https://doc.ibexa.co/projects/userguide/en/4.4/content_management/block_reference/#recently-added-block) displays a list of products that were recently added to the product catalog. ### Personalization improvements #### Automated way of creating Personalization service account The Personalization service has been enhanced to speed up the process of creating a new customer account. Now, to create an account in the new, automated way, you have to fill out the form, select an account type, and send a request to the Personalization endpoint. Shortly after, you receive the credentials. For more information, see [Requesting access to the server](https://doc.ibexa.co/projects/userguide/en/4.4/personalization/enable_personalization/#request-access-to-the-server). #### New models in Personalization engine Personalization engine introduces two new recommendation models: [predictive](https://doc.ibexa.co/projects/userguide/en/4.4/personalization/recommendation_models/#predictive) and [recurring purchase](https://doc.ibexa.co/projects/userguide/en/4.4/personalization/recommendation_models/#recurring-purchase). These two new models, based on mathematical approach, help to predict clients behavior and provide the best recommendations. ## Ibexa Connect You can now take advantage of [Ibexa Connect](https://www.ibexa.co/products/ibexa-connect), an iPaaS (integration platform-as-a-service) which allows you to connect Ibexa DXP with third-party applications. Ibexa Connect features a low-code drag-and-drop interface and hundreds of connectors to different services that help you automate business processes. See [Ibexa Connect documentation](https://doc.ibexa.co/projects/connect/en/latest/). *[Image: Example of an Ibexa Connect scenario]* ## Other changes ### Flysystem v2 The codebase has undergone significant upgrades to rely on Flysystem v2. The Flysystem Adapter implementation now supports dynamic paths described by complex settings resolvable for the SiteAccess context. For more information, see [Configuring the DFS IO handler](https://doc.ibexa.co/en/4.4/infrastructure_and_maintenance/clustering/clustering/#configuring-the-dfs-io-handler). If your custom project relies directly on a Flysystem features instead of using our IO abstraction, it requires an upgrade as well, performed according to [these instructions](https://flysystem.thephpleague.com/docs/upgrade-from-1.x/). ### Dedicated migration type for Corporate Accounts To simplify data migration, you can now create a corporate account with underlying objects such as members group and address book. You can also extract those objects as references. For more information on data migration actions, see [Data migration actions](https://doc.ibexa.co/en/4.4/content_management/data_migration/data_migration_actions/#data-migration-actions). ### API improvements ### Item age in Recently added model In a Recently added model (previously Random model), you can now manually [set the age of items](https://doc.ibexa.co/projects/userguide/en/4.4/personalization/recommendation_models/#recently-added) which are displayed in recommendations. ### Deprecations #### Commerce packages The following Commerce packages are deprecated as of this release and will be removed in v5: - `ibexa/commerce-admin-ui` - `ibexa/commerce-erp-admin` - `ibexa/commerce-order-history` - `ibexa/commerce-page-builder` - `ibexa/commerce-rest` - `ibexa/commerce-transaction` - `ibexa/commerce-base-design` - `ibexa/commerce-checkout` - `ibexa/commerce-fieldtypes` - `ibexa/commerce-price-engine` - `ibexa/commerce-shop` - `ibexa/commerce-shop-ui` They will be maintained by {{ product_name_name }} with fixes, including security fixes, but they won't be further developed. Old packages are replaced by [the all-new Ibexa Commerce packages](#all-new-ibexa-commerce-packages) with more to come in the upcoming releases. #### Flysystem - Support for overwriting existing files has been dropped (catch block of `\Ibexa\Core\IO\IOBinarydataHandler\Flysystem::create` and test). The new native Flysystem v2 Local Adapter performs this out of the box. - Support for no last modified timestamp has been dropped (in the form of a test case). The new Flysystem v2 throws `UnableToRetrieveMetadata` exception in such case. ## Full changelog | Ibexa Content | Ibexa Experience | Ibexa Commerce | | -------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | | [Ibexa Content v4.4](https://github.com/ibexa/content/releases/tag/v4.4.0) | [Ibexa Experience v4.4](https://github.com/ibexa/experience/releases/tag/v4.4.0) | [Ibexa Commerce v4.4](https://github.com/ibexa/commerce/releases/tag/v4.4.0) | # Ibexa DXP v4.3 **Version number**: v4.3 **Release date**: November 10, 2022 **Release type**: [Fast Track](https://support.ibexa.co/Public/service-life) **Update**: [v4.2.x to v4.3](https://doc.ibexa.co/en/4.3/update_and_migration/from_4.2/update_from_4.2/) ## Notable changes ### Customer Portal (Experience) (Commerce) #### Company self-registration Now, a prospective buyer can apply to [create a company account](https://doc.ibexa.co/projects/userguide/en/latest/shop_administration/company_self_registration/) on a seller's website. The application goes through an approval process where admin specifies the customer group and sales representative for the new company account. Finally, the invitation link is sent back to the applicant to finish the registration process and give them access to the Customer Portal. For more information, see [Customer Portal applications documentation](https://doc.ibexa.co/en/latest/customer_management/cp_applications/index.md). *[Image: Company self-registration]* #### Customization of approval process You can now [customize the approval process](https://doc.ibexa.co/en/latest/customer_management/cp_applications/#customization-of-an-approval-process) for company self-registration. By adding additional steps and options, you can build a process that perfectly meets your business needs. ### SEO configuration exposed SEO configuration gains a more prominent place on the content type editing screen. For example, to enable SEO, you now have to edit the content type that you want to modify, scroll down to the SEO section and switch the **Enable SEO for this content type** toggle. For more information, see [Work with SEO](https://doc.ibexa.co/projects/userguide/en/latest/search_engine_optimization/work_with_seo/). > **Note: Note** > > This change is also implemented in v4.2. ## Other changes ### Product catalog improvements #### Price Sort Clauses When querying for products, you can now use one of two price-related Sort Clauses: - [`BasePrice` Sort Clause](https://doc.ibexa.co/en/master/search/sort_clause_reference/baseprice_sort_clause/) sorts results by the products' base prices - [`CustomPrice` Sort Clause](https://doc.ibexa.co/en/master/search/sort_clause_reference/customprice_sort_clause/) enables sorting by the custom price configured for the provided customer group. #### Usability improvements This release also includes a number of usability improvements in the product catalog, such as full information about available attribute values or improved display of Selection attributes. You can now move assets between collections by using drag and drop. *[Image: Moving assets between collection with drag and drop]* From product's **Completeness** tab you can now jump directly to editing the product prices in all configured currencies. *[Image: Editing product price from Completeness tab]* #### Catalog filters In catalogs, you can now [configure default filters](https://doc.ibexa.co/en/master/pim/pim_configuration/#catalog-filters) that are always added to a catalog, define filter order, and group custom filters. Built-in filters are also divided into groups now for easier browsing. Filtering by the Color attribute is now possible. #### Integration with recommendation engine Now, during product creation, edition, or deletion, information about the selected product categories (Taxonomies) is sent to the recommendation engine as an attribute and can be used for recommendation engine filtering. ### Users #### New User content type This release brings you a new content type for private customers registering from the front page. We also prepared a migration command for already existing users to ease your upgrade process. For more information, refer to upgrade documentation. ### API improvements The catalogs functionality in the product catalog is now covered in REST API, including: - [Getting catalog list](https://doc.ibexa.co/en/4.3/api/rest_api/rest_api_reference/rest_api_reference.html#product-catalog-filter-catalogs) - [Creating, modifying, copying and deleting catalogs](https://doc.ibexa.co/en/4.3/api/rest_api/rest_api_reference/rest_api_reference.html#product-catalog-create-catalog) - [Changing catalog status](https://doc.ibexa.co/en/4.3/api/rest_api/rest_api_reference/rest_api_reference.html#product-catalog-update-catalog) - [Getting catalog filters and sorting options](https://doc.ibexa.co/en/4.3/api/rest_api/rest_api_reference/rest_api_reference.html#product-catalog-load-catalog-filters) ### Personalization improvements Now, as a Personalization admin, after editing a model in the back office, [you can build this model](https://doc.ibexa.co/projects/userguide/en/master/personalization/recommendation_models/#trigger-model-build), use the **Trigger model build** button to build this model with your modifications. ### Taxonomy improvements Objects of `Ibexa\Contracts\Taxonomy\Value\TaxonomyEntry` type, which are returned by `TaxonomyService`, now contain the information about nesting level in the tree. The `TaxonomyEntryId` Search Criterion isn't available in Legacy search Engine. ### Other improvements - You can now [customize Elasticsearch index structure](https://doc.ibexa.co/en/master/search/extensibility/customize_elasticsearch_index_structure/) to manage how documents in the index are grouped. - A new [`ibexa_seo_is_empty()` Twig function](https://doc.ibexa.co/en/master/templating/twig_function_reference/content_twig_functions/#ibexa_content_name) checks whether SEO data is available for a content item. ## Full changelog | Ibexa Content | Ibexa Experience | Ibexa Commerce | | -------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | | [Ibexa Content v4.3](https://github.com/ibexa/content/releases/tag/v4.3.0) | [Ibexa Experience v4.3](https://github.com/ibexa/experience/releases/tag/v4.3.0) | [Ibexa Commerce v4.3](https://github.com/ibexa/commerce/releases/tag/v4.3.0) | ## v4.3.1 ### New REST API endpoints You can now use new REST API routes that confirm whether the User is logged in, without invoking any other route: - GET `/user/current` - redirects to current User API load. - GET `/user/sessions/current` - returns a current User Session object. You can retrieve, add and remove users from a Segment with: - GET `/user/users/{userId}/segments` - retrieves Segments for a given User. - POST `/user/users/{userId}/segments` - assigns User to one or more Segments. - DELETE `/user/users/{userId}/segments/{segmentIdentifier}` - unassigns User from a Segment. You can retrieve the defined languages with: - GET `/languages`- returns a defined language list. - GET `/languages/{languageCode}` - returns a single language. ### New service for token-based authentication The new release adds `Ibexa\Contracts\Rest\Security\AuthorizationHeaderRESTRequestMatcher` service that can be used instead of `Ibexa\AdminUi\REST\Security\NonAdminRESTRequestMatcher`. It allows REST API endpoints to work with cookie-based authentication. ### Product catalog improvements #### HTTP cache support for product-related responses Customer group is now part of user context, which enables HTTP cache to support product-related responses. #### Ability to retrieve a customer group You can now retrieve customer group by implementing the `Ibexa\Contracts\ProductCatalog\CustomerGroupResolverInterface` interface and tagging it with `ibexa.product_catalog.customer_group.resolver`. ## v4.3.5 - When `UserService::updateUserPassword` method throws `ContentFieldValidationException`, it now uses the format accessible via `ContentFieldValidationException::getFieldErrors`: ``` array<, array<, array<\Ibexa\Contracts\Core\FieldType\ValidationError>>> ``` # Ibexa DXP v4.2 **Version number**: v4.2 **Release date**: August 9, 2022 **Release type**: [Fast Track](https://support.ibexa.co/Public/service-life) **Update**: [v4.1.x to v4.2](https://doc.ibexa.co/en/latest/update_and_migration/from_4.1/update_from_4.1/) ## Notable changes ### Customer Portal (Experience) (Commerce) The new Customer Portal allows you to create and manage a business account for your company. With this new feature, you can easily manage members of your organization, your shipping information and view your past orders. You can invite members to your company, activate or deactivate their accounts, assign them specific roles and limitations, such as a buyer, or sales representative, and group them into teams. *[Image: Customer Portal back office]* For more information, see [back office company management documentation](https://doc.ibexa.co/projects/userguide/en/latest/shop_administration/manage_users). On their personal accounts in Customer Portal, members of your organisation can view their order history, other members of their team and information regarding their company, for example, billing addresses. They can also edit their profile information. *[Image: Customer Portal Frontend]* For more information, see [Customer Portal documentation](https://doc.ibexa.co/projects/userguide/en/latest/shop_administration/customer_portal). ### User management #### Inviting users You can [invite users to create their account](https://doc.ibexa.co/projects/userguide/en/latest/users/user_management/#inviting-users) in the frontend as customers or in the back office as members of your team. *[Image: Inviting members of your team]* #### Configure register form Register forms for new users can now be [configured straight in the YAML file](https://doc.ibexa.co/en/latest/guide/content_rendering/layout/add_register_user_template/#configure-existing-form). ### Catalogs You can now create catalogs containing sub-sets of products. Choose products for a catalog by applying filters which enable you to select products, for example, by product type, price range, availability or category. *[Image: List of products in a catalog]* Catalogs are useful when creating special lists for B2B and B2C uses, for retailers and distributors or for different regions, or other situations where you need to present a selected set of products. ### Product variants To cover use cases of products with variable characteristics (such as colors, technical parameters or sizes), you can now create product variants based on selected attributes. The system automatically generates variants for the attribute values you select. *[Image: Generating product variants]* You can set prices, including custom pricing, availability, and stock for each variant separately. ### Product assets To provide your products with images, you can now upload multiple assets to each product. Assets are grouped into collections based on attribute values and, in this way, are connected to product variants which have these attributes. *[Image: Asset images in product view]* ### Product completeness The new product completeness tab, in product view, lists all the parts of a product you can configure, for example, attributes, assets, prices, and availability. You can use it to get a quick overview of missing parts in the product configuration and to instantly move to the proper screen to fill the gaps. *[Image: Product completeness tab]* > **Note: No impact on availability** > > Product completeness helps ensure that product data is complete. It does not impact product availability or visibility on the storefront. As long as a product meets availability and stock requirements, it can be published and made available for purchase regardless of its completeness score. ### Product categories With product categories, you can organize products that populate the Product Catalog. You do it, for example, to assist users in searching for products. For more information, see [Product categories](https://doc.ibexa.co/projects/userguide/en/latest/shop_administration/product_categories/). *[Image: Product categories]* ### Cross-content type (CCT) recommendations If a recommendation scenario has more than one content type configured, with cross-content type (CCT) parameter in the request, you can now get recommendations for all these content types. ### Taxonomy field type Taxonomy is now [configured with a field type](https://doc.ibexa.co/projects/userguide/en/latest/taxonomy/#add-tag), so you can use many Fields to add different taxonomy categories, for example, tags and product categories in the same content type. ### Address field type With the [new Address field type](https://doc.ibexa.co/en/latest/content_management/field_types/field_type_reference/addressfield), you can now customize address Fields and configure them per country. *[Image: Address field type]* ### Repeatable migration steps Data migration now offers [repeatable migration steps](https://doc.ibexa.co/en/latest/guide/data_migration/importing_data/#repeatable-steps), especially useful when creating large amounts of data, for example for testing. You can vary the migration values by using the iteration counter, or by generating random data by using [`FakerPHP`](https://fakerphp.github.io/). ## Other changes ### New product Search Criteria and Sort Clauses New Search Criteria and Sort Clauses help better fine-tune searches for products. Price-related Search Criteria enable you to search by base or custom product price: - [BasePrice](https://doc.ibexa.co/en/latest/guide/search/criteria_reference/baseprice_criterion/) - [CustomPrice](https://doc.ibexa.co/en/latest/guide/search/criteria_reference/customprice_criterion/) Attribute Criteria search for products based on their attribute values, per attribute type: - [CheckboxAttribute](https://doc.ibexa.co/en/latest/guide/search/criteria_reference/checkboxattribute_criterion/) - [ColorAttribute](https://doc.ibexa.co/en/latest/guide/search/criteria_reference/colorattribute_criterion/) - [FloatAttribute](https://doc.ibexa.co/en/latest/guide/search/criteria_reference/floatattribute_criterion/) - [IntegerAttribute](https://doc.ibexa.co/en/latest/guide/search/criteria_reference/integerattribute_criterion/) - [SelectionAttribute](https://doc.ibexa.co/en/latest/guide/search/criteria_reference/selectionattribute_criterion/) - SimpleMeasurementAttribute - RangeMeasurementAttributeMinimum - RangeMeasurementAttributeMaximum Creation date Criteria and Sort Clauses allow searching by date of the product's creation: - [CreatedAt](https://doc.ibexa.co/en/latest/guide/search/criteria_reference/createdat_criterion/) - [CreatedAtRange](https://doc.ibexa.co/en/latest/guide/search/criteria_reference/createdatrange_criterion/) - [CreatedAt](https://doc.ibexa.co/en/latest/guide/search/sort_clause_reference/createdat_sort_clause/) Finally, you can search product by product category: - [ProductCategory](https://doc.ibexa.co/en/latest/guide/search/criteria_reference/productcategory_criterion/) ### API improvements #### GraphQL Taxonomy is now covered with GraphQL API. Querying product attributes with GraphQL is improved with the option to [query by attribute type](https://doc.ibexa.co/en/latest/api/graphql_queries/#querying-product-attributes). ### New ways to add images in Online Editor You can now drag and drop images directly into the Online Editor. To achieve the same result, you can also click the **Upload image** button and select a file from the disk. Images that you upload this way are automatically added to the Media library. > **Note: Note** > > In Media library, to avoid potential conflicts, if several images are added with identical file names, each of them is modified by appending a unique prefix. *[Image: Drag and drop image into the Online Editor]* ### Content edit tabs Content editing screen is now enriched with a [tab switcher](https://doc.ibexa.co/en/latest/administration/back_office/content_tab_switcher/), allowing easy access to metadata such as taxonomies. The view can be extended with custom tabs. *[Image: Tabs in content edit view]* ### Grouped attributes in Page block If a Page block has multiple attributes, you can now group them with the [`nested_attribute` parameter](https://doc.ibexa.co/en/latest/content_management/pages/page_block_attributes/#nested-attribute-configuration). *[Image: Grouped attributes]* ### Search in URL wildcards You can now search through the **URL wildcards** table in the back office. ### Product price events The price engine now dispatches [events related to creating, updating and deleting prices](https://doc.ibexa.co/en/latest/guide/repository/event_reference/catalog_events/#price). ### Data migration #### Migrations for attributes and attribute groups Data migration now supports `attribute` and `attribute_group` types when generating migration files. #### Hide and reveal content actions You can now hide and reveal content items in data migrations by using the [`hide` and `reveal` actions](https://doc.ibexa.co/en/latest/guide/data_migration/data_migration_actions/#available-migration-actions). ### Fastly shielding Ibexa DXP now supports Fastly shielding. ## Deprecations ### Segmentation - `SegmentationService::loadSegmentGroup()` and `SegmentationService::loadSegment()` are now deprecated. Use `SegmentationService::loadSegmentGroupByIdentifier()` and `SegmentationService::loadSegmentByIdentifier()` instead, which take `SegmentGroup` and `Segment` identifier respectively, instead of numerical IDs. - `SegmentationService::updateSegmentGroup()` and `SegmentationService::updateSegment()` now take a `SegmentGroup` and `Segment` objects respectively, instead of numerical IDs. ## Full changelog | Ibexa Content | Ibexa Experience | Ibexa Commerce | | -------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | | [Ibexa Content v4.2](https://github.com/ibexa/content/releases/tag/v4.2.0) | [Ibexa Experience v4.2](https://github.com/ibexa/experience/releases/tag/v4.2.0) | [Ibexa Commerce v4.2](https://github.com/ibexa/commerce/releases/tag/v4.2.0) | ## v4.2.1 ### Ibexa CDP Ibexa Customer Data Center allows you to collect, connect and organize customer data from multiple sources. You can use them to build segments that allow you to create personalized customer experience for your brand. This is a standalone package that you can install along every product edition (Content, Experience, Commerce). Ibexa CDP is also compatible with Ibexa v3.3. *[Image: CDP Control Panel]* For more information, see [Customer Data Platform](https://doc.ibexa.co/en/latest/cdp/cdp/). ### SEO With Search Engine Optimization (SEO) tool, you can optimize your website or online store for both visitors and search engines. The implementation of SEO brings in more organic traffic and improves your website visibility in SERPs. This is a core feature of Digital Experience Platform. SEO bundle provides meta tags and meta titles with a description which helps to control search result's appearance of your website on the search engine pages. Now you can share your content on the social networks using OpenGraph and Twitter cards. ### Separate product edition directories Thanks to splitting SQL upgrade files into separate product editions, the product update is easier. ### Event layer for TaxonomyService Now, events are sent while performing operations within Taxonomy, which considerably extends the Taxonomy feature. ### Protected segment groups You can now set existing [segment groups](https://doc.ibexa.co/en/latest/administration/admin_panel/segments_admin_panel/) as protected, and prevent them from being modified through the user interface. It's intended to stop users from breaking data integrity of segments/segment groups maintained by other features or external system integrations, such as [Customer Portal](https://doc.ibexa.co/en/latest/customer_management/customer_portal/) and [CDP](https://doc.ibexa.co/en/latest/cdp/cdp/). To do it, in your configuration, add the following key for each segment group that you intend to protect: `ibexa.system.default.segmentation.segment_groups.list..protected` When you change a value of the setting to `true`, users are no longer able to: - remove the segment group or change its name or identifier - add/remove/modify segments that belong to the segment group # Ibexa DXP v4.1 **Version number**: v4.1 **Release date**: April 15, 2022 **Release type**: [Fast Track](https://support.ibexa.co/Public/service-life) ## Notable changes ### Product catalog enhancements With this release, product catalog brings new PHP APIs, productivity boost from new product Search Criteria and Sort classes, advanced filtering in REST endpoints, auto-generated identifiers, product list sorting, and more. You can now use [advanced filtering on products, product types, attributes, and others in REST endpoints](https://doc.ibexa.co/en/latest/api/rest_api_reference/rest_api_reference.html#product-catalog-filter-currencies). Currencies, regions and customer groups can now be resolved automatically in the PHP API based on the current context (for example, selected locale). A new Color attribute enables adding a product attribute that uses the color picker to select a precise color. The product catalog is now fully integrated with the transactional system integration, enabling a full purchasing process. ### Measurement field type and attribute With the new Measurement field type users can now add a Measurement Field, with different pre-built units, to content: *[Image: Adding a Measurement Field to content type definition]* The new Measurement product attribute enables describing products with different types and units of measurement: *[Image: Adding measurement attribute values to product]* ### Dynamic targeting block (Experience) (Commerce) [Dynamic targeting block](https://doc.ibexa.co/projects/userguide/en/latest/site_organization/working_with_page/#dynamic-targeting-block) for the Page Builder provides recommendation items based on users related to the configured Segments. *[Image: Dynamic targeting block]* ### User interface improvements Several improvements to the back office interface enhance the user experience. These include: - "Go to top" button - new DateTime widget - view switcher between lists, grids and calendar. Several new options have been added to the content tree's contextual menu, including Hide/Reveal, Create, Edit and Add translation, Add/Remove from bookmarks. *[Image: New content tree options]* ## Other changes ### GraphlQL Product catalog is now fully covered in GraphQL API. ### Taxonomy language switcher A language switcher in Taxonomy view enables quick switching between different translations of the tag tree. *[Image: Language switcher in Taxonomy tree]* ### Image optimization Images modified in the Image Editor are now optimized for reduced file size. You can use external libraries to [optimize different image formats](https://doc.ibexa.co/en/latest/guide/images/#image-optimization). ### Expanded data migrations [Data migration](https://doc.ibexa.co/en/latest/content_management/data_migration/data_migration/index.md) now covers additional objects: - [database settings](https://doc.ibexa.co/en/latest/guide/data_migration/importing_data/#settings) - [segments](https://doc.ibexa.co/en/latest/guide/data_migration/importing_data/#segments) - [prices](https://doc.ibexa.co/en/latest/guide/data_migration/importing_data/#prices) with `create` mode - [settings](https://doc.ibexa.co/en/latest/guide/data_migration/importing_data/#settings) Data migration now also offers a locking capability, which prevents multiple processes from executing the same migration and causing duplicated records. ## Full changelog | Ibexa Content | Ibexa Experience | Ibexa Commerce | | -------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | | [Ibexa Content v4.1](https://github.com/ibexa/content/releases/tag/v4.1.0) | [Ibexa Experience v4.1](https://github.com/ibexa/experience/releases/tag/v4.1.0) | [Ibexa Commerce v4.1](https://github.com/ibexa/commerce/releases/tag/v4.1.0) | # Ibexa DXP v4.0 **Version number**: v4.0 **Release date**: February 4, 2022 **Release type**: [Fast Track](https://support.ibexa.co/Public/service-life) ## Notable changes ### Redesigned user interface The the back office has undergone a complete redesign, including revised look and feel, simplified navigation and more streamlined workflows. *[Image: New UI]* > **Tip: Tip** > > Read more about the rationale and process for the redesign on [Ibexa blog](https://www.ibexa.co/blog/ibexa-dxp-v4.0-preview-redesigned-user-interface-elevates-the-user-experience). ### New product catalog New product catalog enables easy management of products, stock and prices. Products are now organized into product types, each offering a specific set of attributes that you can use to provide information about a product. You can also set VAT rates per product type. *[Image: Product catalog]* #### Price management You can now configure prices with discounts per product and per customer group. Separate currencies enable you to set different price rules for different currencies. *[Image: Price management]* ### Taxonomy management You can now organize content adding tags and create taxonomy categories to make it easy for your site users to browse and to deliver content appropriate for them. ### Separate recommendations for different websites Personalization service has been enhanced to allow returning separate recommendations for different websites. This way you can eliminate irrelevant recommendations when you set up stores that operate on different markets or under different brands. For more information, see [Support for multiple websites](https://doc.ibexa.co/projects/userguide/en/latest/personalization/use_cases/#multiple-website-hosting). ## Other changes ### Draft locking You can now configure and use the locking feature to lock a draft of a content item, so that only an assigned person can edit it, and no other user can take it over. For more information, see the [Draft locking](https://doc.ibexa.co/en/latest/guide/workflow/workflow/#draft-locking) and relevant [User Documentation](https://doc.ibexa.co/projects/userguide/en/latest/publishing/editorial_workflow/#releasing-locked-drafts). ### Online Editor is now based on CKEditor You can now edit content of RichText Fields using CKEditor and extend its functionality with many elements. For more information, see [Extend Online Editor](https://doc.ibexa.co/en/latest/extending/extending_online_editor/). ### Enhanced GraphQL location handling GraphQL now enables better querying of Locations and URLs. ### Migration API You can now manage [data migrations](https://doc.ibexa.co/en/latest/guide/data_migration/data_migration/) by using the PHP API, including getting migration information and running individual migration files. See [Managing migrations](https://doc.ibexa.co/en/latest/api/public_php_api_managing_migrations/) for more information. ### Decide whether alternative text for Image field is optional Alternative text for an Image field is now optional by default. You can set it as required when adding the Image field to a content type. ### Configure what elements are available in the Page Builder for the content type (Experience) (Commerce) You can now select which page blocks, page layout and what edit mode are available in the Editor mode for the content type. For more information, see [Working with Page](https://doc.ibexa.co/projects/userguide/en/latest/site_organization/working_with_page/#configure-block-display). ### Purge all submissions of given form (Experience) (Commerce) You can purge all submissions of a given form. For more information, see [Forms](https://doc.ibexa.co/en/latest/guide/form_builder/forms/#form-submission-purging). ### External datasource handling Personalization has been given an option to fetch content feed from external sources. ### Category exclusion Personalization service has been enhanced with a feature which allows to exclude categories from the recommendation response. See [Exclusions](https://doc.ibexa.co/projects/userguide/en/latest/personalization/filters/#exclusions). ## Deprecations ### Code cleanup results v4.0 sees significant code cleanup, including renaming of namespaces, services, REST API endpoints and many other internal names. Refer to [Ibexa DXP v4.0 deprecations and backwards compatibility breaks](https://doc.ibexa.co/en/latest/release_notes/ibexa_dxp_v4.0_deprecations/index.md) for full details of changes and how they influence your project. ## Full changelog | Ibexa Content | Ibexa Experience | Ibexa Commerce | | -------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | | [Ibexa Content v4.0](https://github.com/ibexa/content/releases/tag/v4.0.0) | [Ibexa Experience v4.0](https://github.com/ibexa/experience/releases/tag/v4.0.0) | [Ibexa Commerce v4.0](https://github.com/ibexa/commerce/releases/tag/v4.0.0) | # Ibexa DXP v4.0 deprecations and backwards compatibility breaks Ibexa DXP v4.0 introduces changes to significant parts of the code to align with the product name change from earlier eZ Platform. These changes include changing repository names, namespaces, filenames, function names, and others. A backwards compatibility layer ensures that custom implementations and extensions using the older naming should function without change. ## Namespaces Namespaces in the product which referred to old product names now use the Ibexa name. All namespace changes are listed in the `ibexa/compatibility-layer` repository. Refer to [mapping reference](https://github.com/ibexa/compatibility-layer/tree/main/src/bundle/Resources/mappings) for a full comparison of old and new bundle names and namespaces. > **Tip: Tip** > > To make sure your code is up to date with the new namespaces, you can use the [Ibexa PhpStorm plugin](https://doc.ibexa.co/en/latest/resources/phpstorm_plugin/index.md). The plugin indicates deprecated namespaces and suggests updating them to new ones. ### Richtext namespace The internal format of richtext has changed. All namespace changes are listed in the [richtext](https://github.com/ibexa/fieldtype-richtext/blob/bf45e57ea1d2933cc02eb8d8bff76c0925de92de/src/bundle/Resources/config/default_settings.yaml#L60-L67) repository. ## Configuration keys `ezplatform` and `ezpublish` configuration keys have been replaced with `ibexa`. Other package-specific configuration keys have also been updated. | Old name | New name | | ------------------------------------------------- | ------------------------------------------------------------ | | `ezplatform` | `ibexa` | | `ezpublish` | `ibexa` | | `ez_doctrine_schema` | `ibexa_doctrine_schema` | | `ez_io` | `ibexa_io` | | `ez_platform_fastly_cache` | `ibexa_fastly` | | `ez_platform_http_cache` | `ibexa_http_cache` | | `ez_platform_page_builder` | `ibexa_page_builder` | | `ez_platform_standard_design` | `ibexa_standard_design` | | `ez_search_engine_legacy` | `ibexa_legacy_search_engine` | | `ez_search_engine_solr` | `ibexa_solr` | | `ezdesign` | `ibexa_design_engine` | | `ezplatform_elastic_search_engine` | `ibexa_elasticsearch` | | `ezplatform_form_builder` | `ibexa_form_builder` | | `ezplatform_graphql` | `ibexa_graphql` | | `ezplatform_page_fieldtype` | `ibexa_fieldtype_page` | | `ezplatform_support_tools` | `ibexa_system_info` | | `ezrecommendation` | `ibexa_personalization_client` | | `ezrichtext` | `ibexa_fieldtype_richtext` | | `ezrichtext.custom_styles..is_inline` | `ibexa_fieldtype_richtext.custom_styles..inline` | | `ibexa_platform_commerce_field_types` | `ibexa_commerce_field_types` | | `one_sky` | `ibexa_commerce_one_sky` | | `ses_specificationstypefieldtype` | `ibexa_commerce_specifications_type` | | `shop_price_engine_plugin` | `ibexa_commerce_price_engine` | | `silversolutions_eshop` | `ibexa_commerce_eshop` | | `silversolutions_tools` | `ibexa_commerce_shop_tools` | | `silversolutions_translation` | `ibexa_commerce_translation` | | `siso_admin_erp` | `ibexa_commerce_erp_admin` | | `siso_basket` | `ibexa_commerce_basket` | | `siso_checkout` | `ibexa_commerce_checkout` | | `siso_comparison` | `ibexa_commerce_comparison` | | `siso_content_plugin` | `ibexa_commerce_base_design` | | `siso_ez_studio` | `ibexa_commerce_ez_studio` | | `siso_local_order_management` | `ibexa_commerce_local_order_management` | | `siso_newsletter` | `ibexa_commerce_newsletter` | | `siso_order_history` | `ibexa_commerce_order_history` | | `siso_payment` | `ibexa_commerce_payment` | | `siso_price` | `ibexa_commerce_price` | | `siso_quick_order` | `ibexa_commerce_quick_order` | | `siso_search` | `ibexa_commerce_search` | | `siso_shop_frontend` | `ibexa_commerce_shop_frontend` | | `siso_test` | `ibexa_commerce_test_tools` | | `siso_tools` | `ibexa_commerce_tools` | | `siso_voucher` | `ibexa_commerce_voucher` | ## Service names Service names which referred to old product names now use the Ibexa name. All service name changes are listed in the `ibexa/compatibility-layer` repository. Refer to [mapping reference](https://github.com/ibexa/compatibility-layer/blob/main/src/bundle/Resources/mappings/services-to-fqcn-map.php) for a full comparison of old and new names. ## Service tags Service tag which referred to old product names now use the Ibexa name. All service tag changes are listed in the `ibexa/compatibility-layer` repository. Refer to [mapping reference](https://github.com/ibexa/compatibility-layer/blob/main/src/bundle/Resources/mappings/symfony-service-tag-name-map.php) for a full comparison of old and new service tags. ## CSS classes for back office CSS classes with the `ez-` prefix have been modified with an `ibexa-` prefix. ## JavaScript event names JavaScript event names with the `ez-` prefix have been modified with an `ibexa-` prefix, for example: `ez-notify` > `ibexa-notify` `ez-content-tree-refresh` > `ibexa-content-tree-refresh` ## REST API REST API route prefix has changed from `/api/ezp/v2/` to `/api/ibexa/v2/`. REST API media types have changed from `application/vnd.ez.api.*` to `application/vnd.ibexa.api.*`. ## Twig functions and filters The following Twig functions and filter have been renamed, including: | Old name | New name | | ----------------- | -------------------- | | `ez_content_name` | `ibexa_content_name` | | `ez_render_field` | `ibexa_render_field` | | `ez_render` | `ibexa_render` | | `ez_field` | `ibexa_field` | | `ez_image_alias` | `ibexa_image_alias` | Full list of changed Twig function and filter names | Old name | New name | | ------------------------------------------------ | --------------------------------------------------- | | `calculate_shipping` | `ibexa_commerce_calculate_shipping` | | `code_label` | `ibexa_commerce_code_label` | | `date_format` | `ibexa_commerce_date_format` | | `ez_content_field_identifier_first_filled_image` | `ibexa_content_field_identifier_first_filled_image` | | `ez_content_field_identifier_image_asset` | `ibexa_content_field_identifier_image_asset` | | `ez_content_name` | `ibexa_content_name` | | `ez_content_type_icon` | `ibexa_content_type_icon` | | `ez_data_attributes_serialize` | `ibexa_data_attributes_serialize` | | `ez_datetime_diff` | `ibexa_datetime_diff` | | `ez_field_description` | `ibexa_field_description` | | `ez_field_is_empty` | `ibexa_field_is_empty` | | `ez_field_name` | `ibexa_field_name` | | `ez_field_value` | `ibexa_field_value` | | `ez_field` | `ibexa_field` | | `ez_file_size` | `ibexa_file_size` | | `ez_full_date` | `ibexa_full_date` | | `ez_full_datetime` | `ibexa_full_datetime` | | `ez_full_time` | `ibexa_full_time` | | `ez_http_cache_tag_location` | `ibexa_http_cache_tag_location` | | `ez_http_tag_location` | `ibexa_http_cache_tag_location` | | `ez_http_tag_relation_ids` | `ibexa_http_cache_tag_relation_ids` | | `ez_http_tag_relation_location_ids` | `ibexa_http_cache_tag_relation_location_ids` | | `ez_image_alias` | `ibexa_image_alias` | | `ez_page_layout` | `ibexa_page_layout` | | `ez_path_to_locations` | `ibexa_path_to_locations` | | `ez_path` | `ibexa_path` | | `ez_recommendation_enabled` | `ibexa_recommendation_enabled` | | `ez_recommendation_track_user` | `ibexa_recommendation_track_user` | | `ez_render_*_query_*` | `ibexa_render_*_query_` | | `ez_render_*_query` | `ibexa_render_*_query` | | `ez_render_comparison_result` | `ibexa_render_comparison_result` | | `ez_render_content` | `ibexa_render_content` | | `ez_render_field_definition_settings` | `ibexa_render_field_definition_settings` | | `ez_render_field` | `ibexa_render_field` | | `ez_render_limitation_value` | `ibexa_render_limitation_value` | | `ez_render_location` | `ibexa_render_location` | | `ez_render` | `ibexa_render` | | `ez_richtext_to_html5_edit` | `ibexa_richtext_to_html5_edit` | | `ez_richtext_to_html5` | `ibexa_richtext_to_html5` | | `ez_richtext_youtube_extract_id` | `ibexa_richtext_youtube_extract_id` | | `ez_route` | `ibexa_route` | | `ez_short_date` | `ibexa_short_date` | | `ez_short_datetime` | `ibexa_short_datetime` | | `ez_short_time` | `ibexa_short_time` | | `ez_url` | `ibexa_url` | | `get_characteristics_b2b` | `ibexa_commerce_get_characteristics_b2b` | | `get_relation_content` | `ibexa_commerce_get_relation_content` | | `get_search_query` | `ibexa_commerce_get_search_query` | | `get_shipping_free_value` | `ibexa_commerce_get_shipping_free_value` | | `get_siteaccess_locale` | `ibexa_commerce_get_siteaccess_locale` | | `get_stored_baskets` | `ibexa_commerce_get_stored_baskets` | | `ibexa_commerce_render_stock` | `ibexa_commerce_render_stock` | | `ibexa_platform_asset` | `ibexa_dam_asset` | | `ibexa_platform_dam_image_transformation` | `ibexa_dam_image_transformation` | | `is_shipping_free` | `ibexa_commerce_is_shipping_free` | | `price_format` | `ibexa_commerce_price_format` | | `ses_assets_by_group` | `ibexa_commerce_assets_by_group` | | `ses_assets_image_list` | `ibexa_commerce_assets_image_list` | | `ses_assets_main_image` | `ibexa_commerce_assets_main_image` | | `ses_basket` | `ibexa_commerce_basket` | | `ses_check_product_in_comparison` | `ibexa_commerce_check_product_in_comparison` | | `ses_check_product_in_wish_list` | `ibexa_commerce_check_product_in_wish_list` | | `ses_comparison_category` | `ibexa_commerce_comparison_category` | | `ses_config_parameter` | `ibexa_commerce_config_parameter` | | `ses_contains_basket_vouchers` | `ibexa_commerce_contains_basket_vouchers` | | `ses_content_pagination` | `ibexa_commerce_content_pagination` | | `ses_correct_url` | `ibexa_commerce_correct_url` | | `ses_erp_to_default` | `ibexa_commerce_erp_to_default` | | `ses_format_args` | `ibexa_commerce_format_args` | | `ses_get_basket_vouchers` | `ibexa_commerce_get_basket_vouchers` | | `ses_invoice_number` | `ibexa_commerce_invoice_number` | | `ses_navigation` | `ibexa_commerce_navigation` | | `ses_pagination` | `ibexa_commerce_pagination` | | `ses_product` | `ibexa_commerce_product` | | `ses_render_field` | `ibexa_commerce_render_field` | | `ses_render_price` | `ibexa_commerce_render_price` | | `ses_render_specification_matrix` | `ibexa_commerce_render_specification_matrix` | | `ses_render_stock` | `ibexa_commerce_render_stock` | | `ses_scope_request_active` | `ibexa_commerce_scope_request_active` | | `ses_to_float` | `ibexa_commerce_to_float` | | `ses_total_comparison` | `ibexa_commerce_total_comparison` | | `ses_track_base` | `ibexa_commerce_track_base` | | `ses_track_basket` | `ibexa_commerce_track_basket` | | `ses_track_product` | `ibexa_commerce_track_product` | | `ses_user_menu` | `ibexa_commerce_user_menu` | | `ses_variant_product_by_sku` | `ibexa_commerce_variant_product_by_sku` | | `ses_wish_list` | `ibexa_commerce_wish_list` | | `sort_characteristic_codes` | `ibexa_commerce_sort_characteristic_codes` | | `sort_characteristics` | `ibexa_commerce_sort_characteristics` | | `st_image` | `ibexa_commerce_image` | | `st_imageconverter` | `ibexa_commerce_imageconverter` | | `st_resolve_template` | `ibexa_commerce_resolve_template` | | `st_siteaccess_lang` | `ibexa_commerce_siteaccess_lang` | | `st_siteaccess_path` | `ibexa_commerce_siteaccess_path` | | `st_siteaccess_url` | `ibexa_commerce_siteaccess_url` | | `st_tag` | `ibexa_commerce_tag` | | `st_translate` | `ibexa_commerce_translate` | | `truncate` | `ibexa_commerce_truncate` | | `unserialize` | `ibexa_commerce_unserialize` | | `youtube_video_id` | `ibexa_commerce_youtube_video_id` | ## URL Alias route name URL Alias route name has changed from `ez_urlalias` to `ibexa.url.alias`. ## Configuration file names Built-in configuration files starting with `ezplatform` now use names with `ibexa`, including: | Old name | New name | | --------------------------------------- | --------------------------- | | `ezplatform.yaml` | `ibexa.yaml` | | `ezplatform_admin_ui.yaml` | `ibexa_admin_ui.yaml` | | `ezplatform_assets.yaml` | `ibexa_assets.yaml` | | `ezplatform_doctrine_schema.yaml` | `ibexa_doctrineschema.yaml` | | `ezplatform_elastic_search_engine.yaml` | `ibexa_elasticsearch.yaml` | | `ezplatform_form_builder.yaml` | `ibexa_form_builder.yaml` | | `ezplatform_http_cache.yaml` | `ibexa_http_cache.yaml` | | `ezplatform_http_cache_fastly.yaml` | `ibexa_fastly.yaml` | | `ezplatform_page_builder.yaml` | `ibexa_page_builder.yaml` | | `ezplatform_site_factory.yaml` | `ibexa_site_factory.yaml` | | `ezplatform_solr.yaml` | `ibexa_solr.yaml` | | `ezplatform_welcome_page.yaml` | `ibexa_welcome_page.yaml` | ## Minor changes - `AbstractBuilder::createMenuItem` return type is now `ItemInterface` only. - Meaningless properties added to the Doctrine schema now throw an exception. This is prevents indexes from being placed erroneously in the root table. - The following deprecated service tags have been dropped: `ezsystems.platformui.application_config_provider`, `ezpublish.content_view_provider`, `ezpublish.fieldType`, `ezpublish.fieldType.parameterProvider`, `ezpublish.fieldType.indexable`, `ezpublish.fieldType.externalStorageHandler`, `ezpublish.fieldType.externalStorageHandler.gateway`, `ezpublish.location_view_provider`, `ezpublish.query_type`, `ezpublish.searchEngineIndexer`, `ezpublish.searchEngine`, `ezpublish.storageEngine.legacy.converter` - `Ibexa\Contracts\Core\MVC\EventSubscriber\onConfigScopeChange::onConfigScopeChange` now takes `ScopeChangeEvent $event` instead of `SiteAccess $siteAccess` as argument. # Ibexa DXP v3.3 **Version number**: v3.3 **Release date**: January 18, 2021 **Release type**: [Long Term Support](https://support.ibexa.co/Public/service-life) ## Notable changes ### New Personalization UI This release brings a completely reconstructed user interface of the Personalization feature. *[Image: Personalization dashboard]* ### Symfony Flex Ibexa DXP is now installed using [Symfony Flex](https://symfony.com/doc/7.4/quick_tour/flex_recipes.html). See [the updated installation instruction](https://doc.ibexa.co/en/3.3/getting_started/install_ez_platform) for a new guide to installing the product. ### Image Editor With the Image Editor, users can now perform basic operations, such as cropping or flipping an image, or setting a point of focus. The Image Editor is available when browsing the Media library, or creating or editing content items that contain an `ezimage` or `ezimageasset` Field. You can modify the Image Editor's default settings to change its appearance or behavior. For more information, see [Configuring the Image Editor](https://doc.ibexa.co/en/3.3/guide/image_editor). ### Migration bundle The new [migration bundle](https://doc.ibexa.co/en/3.3/guide/data_migration) enables you to export and import your Repository data by using YAML files. ## Other changes ### Extended Search API capabilities Search API has been extended with the following capabilities: - [Score Sort Clause](https://doc.ibexa.co/en/3.3/guide/search/sort_clause_reference/score_sort_clause) orders search results by their score. - [CustomField Sort Clause](https://doc.ibexa.co/en/3.3/guide/search/sort_clause_reference/customfield_sort_clause) sorts search results by raw search index fields. - [ContentTranslatedName Sort Clause](https://doc.ibexa.co/en/3.3/guide/search/sort_clause_reference/contenttranslatedname_sort_clause) sorts search results by the content items' translated names. You can now access [additional search result data from PagerFanta](https://doc.ibexa.co/en/3.3/api/public_php_api_search/#additional-search-result-data). ### PHP API improvements You can now use the following new PHP API methods: - [`UserService::loadUserGroupByRemoteId`](https://github.com/ezsystems/ezplatform-kernel/blob/master/eZ/Publish/API/Repository/UserService.php#L71) - [`PasswordHashService::getDefaultHashType`](https://github.com/ezsystems/ezplatform-kernel/blob/master/eZ/Publish/API/Repository/PasswordHashService.php#L18) - [`PasswordHashService::getSupportedHashTypes`](https://github.com/ezsystems/ezplatform-kernel/blob/master/eZ/Publish/API/Repository/PasswordHashService.php#L25) - [`PasswordHashService::isHashTypeSupported`](https://github.com/ezsystems/ezplatform-kernel/blob/master/eZ/Publish/API/Repository/PasswordHashService.php#L30) - [`PasswordHashService::createPasswordHash`](https://github.com/ezsystems/ezplatform-kernel/blob/master/eZ/Publish/API/Repository/PasswordHashService.php#L37) - [`PasswordHashService::isValidPassword`](https://github.com/ezsystems/ezplatform-kernel/blob/master/eZ/Publish/API/Repository/PasswordHashService.php#L44) ### Query Field Location handling The [Query field type](https://doc.ibexa.co/en/3.3/guide/content_rendering/queries_and_controllers/content_queries/#content-query-field) now enables getting results for the current Location of a content item. ## Deprecations ### Trusted proxy configuration If you configure trusted proxies in the `.env` file, you now need to add them to the configuration in the following way: ``` framework: trusted_proxies: '%env(TRUSTED_PROXIES)%' ``` ## Full changelog See [list of changes in Symfony 5.2](https://symfony.com/blog/symfony-5-2-curated-new-features). | Ibexa Content | Ibexa Experience | Ibexa Commerce | | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | | [Ibexa Content v3.3.0](https://github.com/ibexa/content/releases/tag/v3.3.0) | [Ibexa Experience v3.3.0](https://github.com/ibexa/experience/releases/tag/v3.3.0) | [Ibexa Commerce v3.3.0](https://github.com/ibexa/commerce/releases/tag/v3.3.0) | ## v3.3.15 ### Symfony 5.4 The version v3.3.15 moves Ibexa DXP to Symfony 5.4. For more information, see [Symfony 5.4 documentation](https://symfony.com/releases/5.4) and [update documentation](https://doc.ibexa.co/en/latest/update_and_migration/from_3.3/update_from_3.3/#v3315). # Ibexa DXP v3.2 **Version number**: v3.2 **Release date**: October 23, 2020 **Release type**: Fast Track ## Notable changes *[Image: New login page]* ### New UI This version offers a completely reworked user interface, covering all of the back office, including eCommerce administration. *[Image: New Content structure]* *[Image: Commerce administration]* ### DAM connector You can now [connect your installation to a Digital Asset Management (DAM) system](https://doc.ibexa.co/en/latest/guide/config_connector/#dam-cofniguration) and use assets such as images directly from the DAM in your content. ### Autosave Ibexa Platform can now save your edits in a content item or product automatically to help you preserve the progress in an event of a failure. For more information, see [Autosave](https://doc.ibexa.co/projects/userguide/en/latest/publishing/publishing/#autosave). ### Aggregation API When using Solr or Elasticsearch search engines you can now use aggregations to group search results and get the count of results per aggregation type. You can aggregate results by general conditions such as content type or Section, or by Field aggregations such as the value of specific Fields. For more information, see [Aggregation API](https://doc.ibexa.co/en/latest/api/public_php_api_search/#aggregation). ### Targeting block and Segmentation API (Experience) (Commerce) Targeting block for the Page Builder enables you to display different content items to different users depending on the Segments they belong to. *[Image: Targeting block]* You can [configure Segments](https://doc.ibexa.co/en/latest/guide/admin_panel/#segments) in the back office. [Segmentation API](https://doc.ibexa.co/en/latest/api/public_php_api_managing_users/#segments) enables you to create and edit Segments and Segment Groups, and assign Users to Segments. ### Twig helpers for content rendering Three new Twig helpers are available to make rendering content easier. Use `ez_render_content(content)` and `ez_render_location(location)` to render the selected content item. You can also use `ez_render()` and provide it with either a content or Location object. For more information, see [Using `ez_render` Twig helpers](https://doc.ibexa.co/en/latest/guide/templates/#using-ez_render-twig-helpers). ### JWT authentication You can now use JWT tokens to authenticate in [REST API](https://doc.ibexa.co/en/latest/api/rest_api/rest_api_authentication/#jwt-authentication) and [GraphQL](https://doc.ibexa.co/en/latest/api/graphql/#jwt-authentication). See [JWT authentication](https://doc.ibexa.co/en/latest/guide/security/#jwt-authentication) to learn how to configure this authentication method. ### Searching in Ibexa Commerce with Elasticsearch (Commerce) You can now use Elasticsearch for searching in Ibexa Commerce. See [Install Ibexa Platform](https://doc.ibexa.co/en/latest/getting_started/install_ez_platform/#install-and-configure-a-search-engine) to learn how to install and configure the search engine. ## Other changes ### Site Factory improvements (Experience) (Commerce) You can now define user group skeletons where you define policies and limitations that apply to a specific user group. You can then associate a number of such skeletons with a site template. User group skeletons survive deleting a site. For more information, see [Configure user group skeleton](https://doc.ibexa.co/en/latest/guide/multisite/site_factory_configuration/#user-group-skeletons). ### Calendar widget improvements You can now see the scheduled blocks in the calendar after you configure the reveal and/or hide dates for them. This way you can envision what content will be available in the future. Also, you can now apply new filters that are intended to help you declutter the calendar view. For more information, see [Calendar widget](https://doc.ibexa.co/projects/userguide/en/latest/publishing/advanced_publishing_options/#calendar-widget). ### Cloning content types When creating content types in the back office, you don't have to start from scratch. You can now clone an existing content type instead. To do this, click the **Copy** icon located next to the content type that you want to clone. Then, refresh the view to see an updated list of content types. ### Object state API improvements You can now use `ObjectStateService::loadObjectStateByIdentifier()` and `ObjectStateService::loadObjectStateGroupByIdentifier()` to [get object states and object state groups](https://doc.ibexa.co/en/latest/api/public_php_api_managing_repository/#getting-object-state-information) in the PHP API. ## Full changelog | Ibexa Platform | Ibexa DXP | Ibexa Commerce | | ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | | [Ibexa Platform v3.2.0](https://github.com/ezsystems/ezplatform/releases/tag/v3.2.0) | [Ibexa DXP v3.2.0](https://github.com/ezsystems/ezplatform-ee/releases/tag/v3.2.0) | [Ibexa Commerce v3.2.0](https://github.com/ezsystems/ezcommerce/releases/tag/v3.2.0) | # eZ Platform v3.1 **Version number**: v3.1 **Release date**: July 15, 2020 **Release type**: Fast Track ## Notable changes [eZ Commerce](https://github.com/ezsystems/ezcommerce) now uses Symfony 5 and is fully integrated into the eZ Platform back office. For more information, see [eZ Commerce documentation](https://doc.ezplatform.com/projects/ezcommerce/en/latest/). ## New features This release of eZ Platform introduces the following new features: > **Dxp: Dxp** > > ### Site Factory > > #### Site skeleton > > You can now create multiple content structures that can be used as Site skeletons for the new sites. > > For more information about Site skeleton, see [Configure Site skeleton](https://doc.ibexa.co/en/latest/guide/multisite/site_factory_configuration/#site-skeletons). > > #### Defining parent Location > > You can now define the parent Location for every new site in the template configuration. > > For more information about defining parent Location, see [Configure parent Location](https://doc.ibexa.co/en/latest/guide/multisite/site_factory_configuration/#parent-location). > > ### Elasticsearch > > You can now use [Elasticsearch](https://www.elastic.co/) in your eZ Platform installation through the `PlatformElasticSearchEngineBundle`. > > See [Elasticsearch documentation](https://doc.ibexa.co/en/latest/guide/search/elastic) to learn how to set up, configure and user Elasticsearch with eZ Platform. > > ### Page Builder > > You can now filter elements in the sidebar during site creation process to get to the desired blocks faster. > > ### Field group permissions > > The new [field group limitation](https://doc.ibexa.co/en/latest/guide/limitation_reference/#field-group-limitation) enables you to control who can edit content fields per field group. > > ### Version comparison > > You can now compare additional fields in version comparison of content item: > > - Content Relation and Content Relations > - Image Asset and Image > - Matrix > - Media > > For overview of additional fields, see [User documentation on Comparing versions](https://doc.ibexa.co/projects/userguide/en/5.0/publishing/publishing/#comparing-versions). ### URL management UI You can now manage URL addresses and URL wildcards with a comfortable user interface that is available in the back office. You can create, modify or delete URL wildcards, and decide if the user should be redirected to the new address on clicking the link. > **Note: Note** > > As of this release, the Link manager is no longer part of the Content panel, and now it belongs to the **Admin** panel of the back office. *[Image: URL Management UI]* For more information on how to manage URLs, see [URL management](https://doc.ibexa.co/en/latest/guide/url_management). ### Tree view in the Universal Discovery Widget The Universal Discovery Widget, referred to as the Content Browser in User Documentation, has been updated by adding the Tree view. You can now switch between the Grid, Panels and Tree views to browse and manage user accounts, media files, content items and forms. Selections that you make in one view survive when you switch to the other view. *[Image: Tree view in the Content Browser]* For more information about configuring the Universal Discovery Widget, see [Extending Universal Discovery Widget](https://doc.ibexa.co/en/latest/extending/extending_udw). ### Field group display Display of field groups has been improved in content preview and editing. When editing, field groups are now presented in tabs: *[Image: Field group tabls in content editing]* In Content preview, the group sections are collapsible: *[Image: Collapsible field groups in content view]* ### Saving incomplete draft When users create or edit a content item or a Page, they can now save it without completing all the required fields. They can then return to editing, or pass the content to another contributor. Validation that used to happen at each save operation now, by default, happens when you click the **Publish** button. The `ContentService::validate()` method has been added that you can use to trigger validation of individual fields or whole content items for completeness at other stages of the editing process. ### Search #### ezplatform-search [`ezplatform-search`](https://github.com/ezsystems/ezplatform-search) is a new repository that contains search functionalities that aren't dependent on the search engine. #### Search controller A customizable search controller has been extracted and placed in `ezplatform-search`. #### Searching in trash You can now search through the contents of Trash and sort the search results based on a number of Search Criteria and Sort Clauses that can be used by the `\eZ\Publish\API\Repository\TrashService::findTrashItems` method only. For more information, see [Search in trash](https://doc.ibexa.co/en/latest/api/public_php_api_search/#search-in-trash). ### Repository filtering [Repository filtering](https://doc.ibexa.co/en/latest/api/public_php_api_search/#repository-filtering) enables you to filter content and Locations using a defined Filter, without the `SearchService`. ### PermissionResolver You can now have a Service that provides both `PermissionResolver` and `PermissionCriterionResolver` by injecting `eZ\Publish\API\Repository\PermissionService`. ## Behavior changes ### Landing page drafts When you start creating a landing page, a new draft is now automatically created. ## Deprecations ### Search engine tags The `ezpublish.searchEngine` and `ezpublish.searchEngineIndexer` tags have been deprecated in favor of `ezplatform.search_engine` and `ezplatform.search_engine.indexer`. ## Full changelog | eZ Platform | eZ Enterprise | | --------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | | [eZ Platform v3.1.0](https://github.com/ezsystems/ezplatform/releases/tag/v3.1.0) | [eZ Enterprise v3.1.0](https://github.com/ezsystems/ezplatform-ee/releases/tag/v3.1.0) | | [eZ Platform v3.1.0-rc2](https://github.com/ezsystems/ezplatform/releases/tag/v3.1.0-rc2) | [eZ Enterprise v3.1.0-rc2](https://github.com/ezsystems/ezplatform-ee/releases/tag/v3.1.0-rc2) | | [eZ Platform v3.1.0-rc1](https://github.com/ezsystems/ezplatform/releases/tag/v3.1.0-rc1) | [eZ Enterprise v3.1.0-rc1](https://github.com/ezsystems/ezplatform-ee/releases/tag/v3.1.0-rc1) | | [eZ Platform v3.1.0-beta1](https://github.com/ezsystems/ezplatform/releases/tag/v3.1.0-beta1) | [eZ Enterprise v3.1.0-beta1](https://github.com/ezsystems/ezplatform-ee/releases/tag/v3.1.0-beta1) | # eZ Platform v3.0 **Version number**: v3.0 **Release date**: April 2, 2020 **Release type**: Fast Track ## Overview ## Notable changes ### Symfony 5 The version 3.0 moves eZ Platform to Symfony 5.0 from the previously used Symfony 3.4. This entails several changes to the way projects are organized. For details, see [Symfony 4.0](https://github.com/symfony/symfony/blob/4.0/UPGRADE-4.0.md) and [Symfony 5.0 upgrade documentation](https://github.com/symfony/symfony/blob/5.0/UPGRADE-5.0.md) ### Using Events instead of SignalSlots The application now uses Symfony Events instead of SignalSlots. The application triggers two Events per operation: one before and one after the relevant thing happens (see for example [BookmarkService](https://github.com/ezsystems/ezplatform-kernel/blob/v1.0.0/eZ/Publish/Core/Event/BookmarkService.php)). To use Symfony Events, create [Event Listeners](https://symfony.com/doc/7.4/event_dispatcher.html) in your code. ### New bundles The list of bundles in v3.0 has been extended by the following ones: - [`ezplatform-calendar`](https://github.com/ezsystems/ezplatform-calendar) - [`ezplatform-content-forms`](https://github.com/ezsystems/ezplatform-content-forms) - [`ezplatform-kernel`](https://github.com/ezsystems/ezplatform-kernel) - [`ezplatform-rest`](https://github.com/ezsystems/ezplatform-rest) - [`ezplatform-site-factory`](https://github.com/ezsystems/ezplatform-site-factory) - [`ezplatform-version-comparison`](https://github.com/ezsystems/ezplatform-version-comparison) For details, see [Bundles](https://doc.ibexa.co/en/latest/guide/bundles). ## New features > **Dxp: Dxp** > > ### Site Factory > > The new Site management User Interface is now integrated with back office. It enables you to easily create and manage multiple sites from the back office without editing the configuration files. > > For more information about enabling and configuring, see [Enable Site Factory](https://doc.ibexa.co/en/latest/guide/multisite/site_factory/#enable-site-factory). > > For more information about using the Site Factory, see [User Documentation](https://doc.ibexa.co/projects/userguide/en/5.0/site_organization/site_factory) > > ### Scheduling > > #### Schedule calendar > > You can now easily view and perform scheduling actions with the Calendar widget that is available in the back office. By default, the widget displays content items scheduled for future publication, but custom events can be configured as well. You can also filter displayed events and toggle through a day, week, and month view. > > #### Manage planned publications with Dashboard > > You can now reschedule or cancel planned future publications right from the Dashboard. > > #### Schedule hiding a content item > > You can now schedule hiding content items. Using Calendar widget available in the back office you can also reschedule or cancel hiding a content item. > > ### Defining buttons in Online Editor > > You can now reorder and disable buttons in Online Editor using [YAML configuration](https://doc.ibexa.co/en/latest/extending/extending_online_editor/#customizing-buttons). > > ### Workflow improvements > > #### Workflow actions > > You can now configure your workflows to [automatically publish content](https://doc.ibexa.co/en/latest/guide/workflow/#publishing-content-with-workflow). > > You can also create [custom workflow actions](https://doc.ibexa.co/en/latest/extending/extending_workflow/#adding-custom-actions). > > #### Reviewers > > When sending content through a workflow, the user can now select reviewers. You can require the user to select reviewers when sending content through the workflow. > > In the configuration, you can also set the workflow to [automatically notify the selected reviewers](https://doc.ibexa.co/en/latest/guide/workflow/#sending-notifications). > > #### Quick review > > A built-in Quick Review offers a quick workflow configuration for your basic needs. > > #### Custom transition color > > You can configure a custom color for each of the transitions defined in the Workflow. > > ## Version comparison > > You can now compare two versions of the same content item and preview changes in their Fields: > > *[Image: Version comparison]* ### Universal Discovery Widget The Universal Discovery Widget (UDW) has been re-designed and re-written. New functionalities and changes include: - new configuration - filtered search - resizable column with custom sort order - editing content from UDW (Enterprise only) For full list of changes, see [Backwards compatibility doc](https://doc.ibexa.co/en/latest/releases/ez_platform_v3.0_deprecations/#universal-discovery-widget) and [Configuration](https://doc.ibexa.co/en/latest/extending/extending_udw/#configuration). ### Field types #### Content query field type The new [Content query field type](https://doc.ibexa.co/en/latest/api/field_types_reference/contentqueryfield) enables you to configure a Content query that uses parameters from a Field definition. #### Field type creation You can now use [Generic field type](https://doc.ibexa.co/en/latest/extending/extending_field_type) as a template for your custom field types. #### Keyword field type The `keyword` field type can now recognize versions of a content item. ### Login and password options #### Login by User name or email You can now give your users the ability to [log in with User name or with email](https://doc.ibexa.co/en/latest/users/login_methods). #### Password rules You can now set [password expiration rules](https://doc.ibexa.co/en/latest/guide/user_management/user_management/#password-expiration) for user passwords. ### Duplicate a role You can now duplicate a role with a single click in the back office. *[Image: Duplicate a role]* ### REST API reference The REST reference has been moved from Kernel to a new page, [eZ Platform REST API](https://ezsystems.github.io/ezplatform-rest-reference). ### Search Criteria The following new Search Criteria have been added: | Search Criterion | Search based on | | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ | | [IsUserBased](https://doc.ibexa.co/en/latest/guide/search/criteria_reference/isuserbased_criterion) | Whether content represents a User account | | [IsUserEnabled](https://doc.ibexa.co/en/latest/guide/search/criteria_reference/isuserenabled_criterion) | Whether a User account is enabled | | [ObjectStateIdentifier](https://doc.ibexa.co/en/latest/guide/search/criteria_reference/objectstateidentifier_criterion) | Object state Identifier | | [SectionId](https://doc.ibexa.co/en/latest/guide/search/criteria_reference/sectionid_criterion) | ID of the Section content is assigned to | | [SectionIdentifier](https://doc.ibexa.co/en/latest/guide/search/criteria_reference/sectionidentifier_criterion) | Identifier of the Section content is assigned to | | [UserEmail](https://doc.ibexa.co/en/latest/guide/search/criteria_reference/useremail_criterion) | Email address of a User account | | [Sibling](https://doc.ibexa.co/en/latest/guide/search/criteria_reference/sibling_criterion) | Locations that are children of the same parent | | [UserId](https://doc.ibexa.co/en/latest/guide/search/criteria_reference/userid_criterion) | User ID | | [UserLogin](https://doc.ibexa.co/en/latest/guide/search/criteria_reference/userlogin_criterion) | User login | ### Random sorting The list of common Sort Clauses has been extended by the Random sorting option. ### Contextual Twig variables You can now create [custom Twig variables](https://doc.ibexa.co/en/latest/guide/content_rendering/templates/templates/#custom-template-variables) for use in templates. They can be defined per SiteAccess, or per content view. ### Built-in Query Types Five built-in ready-to-use Query Types have been added: `Children`, `Siblings`, `Ancestors`, `RelatedToContent`, and `GeoLocation`. You can now use the `ez_render_content_query` and `ez_render_location_query` Twig functions to make use of Query Types that don't use the current content or Location. ### Grouping blocks in Page Builder You can now assign Page Builder blocks to groups using the `ezplatform_page_fieldtype.blocks..category` setting. ### Bulk actions in Sub-items list You can now use the Sub-items list to quickly hide, reveal, to add Locations to multiple content items. ### Tooltips You can now add custom tooltips to provide more information for the users when they hover over, focus on, or tap an element. ### Thumbnails The new thumbnails API allows you to easily choose an image for each content. For more information, see [Extending thumbnails](https://doc.ibexa.co/en/latest/extending/extending_thumbnails). ### Type hints for public PHP API Strict types have been added to public PHP API methods. For a complete list, see [backwards compatibility breaks](https://doc.ibexa.co/en/latest/releases/ez_platform_v3.0_deprecations/#strict-types-for-php-api). ## Other changes ### GraphQL In GraphQL, you can now [query Locations and their children](https://doc.ibexa.co/en/latest/api/graphql_queries/#querying-locations). ### Translations #### Improved translating of notifications `TranslationService` isn't injected into the `NotificationService`. You can now use `TranslatableNotificationHandlerInterface` for translated notifications. #### Multilingual content route New multilingual content route for internal translations has been added. ### Renamed templates and parameters Templates and parameters used by the back office have been renamed for consistency. For A full list of changes, see [Backwards compatibility doc](https://doc.ibexa.co/en/latest/releases/ez_platform_v3.0_deprecations). ### HTTP Cache HTTP cache bundle now uses FOS Cache Bundle v2. For a full list of changes this entails, see [Backwards compatibility doc](https://doc.ibexa.co/en/latest/releases/ez_platform_v3.0_deprecations/#ezplatform-http-cache). ### Helpers New helper method `window.eZ.helpers.contentType.getContentTypeName` replaces deprecated `ContentTypeNames`. ### User field type User data is now treated as an external storage. ### SiteAccess-aware Repository The Repository now uses the SiteAccess-aware layer by default. This means that Repository objects are now loaded in the translation corresponding to the SiteAccess. ### REST API Revealing and hiding content can now be performed via REST API. ### PHP API New methods have been introduced to the PHP API: `\eZ\Publish\API\Repository\Values\Content\ContentInfo::getContentType` `\eZ\Publish\API\Repository\Values\Content\ContentInfo::getSection` `\eZ\Publish\API\Repository\Values\Content\ContentInfo::getMainLanguage` `\eZ\Publish\API\Repository\Values\Content\ContentInfo::getMainLocation` `\eZ\Publish\API\Repository\Values\Content\ContentInfo::getOwner` `\eZ\Publish\API\Repository\Values\Content\VersionInfo::getCreator` `\eZ\Publish\API\Repository\Values\Content\VersionInfo::getInitialLanguage` `\eZ\Publish\API\Repository\Values\Content\VersionInfo::getLanguages` `\eZ\Publish\API\Repository\Values\Content\Location::getParentLocation` ## Deprecations and removals For full list of deprecations and removals, see [eZ Platform v3.0 deprecations and backwards compatibility breaks](https://doc.ibexa.co/en/latest/releases/ez_platform_v3.0_deprecations). ### SignalSlots SignalSlots are removed from the application. Use [Event Listeners](https://symfony.com/doc/7.4/event_dispatcher.html) in your code instead. ### Deprecated field types The deprecated `ezprice` and `ezpage` field types have been removed. Nameable field type interface has been removed and replaced by `eZ\Publish\SPI\FieldType\FieldType::getName`. For a full list of changes on field types, see [Backwards compatibility doc](https://doc.ibexa.co/en/latest/releases/ez_platform_v3.0_deprecations/#field-types). ### Elasticsearch Elasticsearch support has been dropped. ### REST server REST-related code has been moved from Kernel to a new [`ezsystems/ezplatform-rest`](https://github.com/ezsystems/ezplatform-rest) package. Following the change, the REST client has been removed from Kernel. ### Kernel `ezpublish-kernel` has been replaced by [`ezplatform-kernel`](https://github.com/ezsystems/ezplatform-kernel). ### Online Editor Online Editor front-end code and assets have been moved to the `ezplatform-richtext` repository. For a full list of resulting changes, see [Backwards compatibility doc](https://doc.ibexa.co/en/latest/releases/ez_platform_v3.0_deprecations/#online-editor). ### Configuration through `ezplatform` In YAML configuration, the main configuration key is now `ezplatform` instead of `ezpublish`. ### Content forms The new `ezplatform-content-forms` package contains forms for content creation moved from `repository-forms`, while content type editing has been moved to `ezplatform-admin-ui` from `repository-forms`. ### Custom Installers The Symfony Service definitions, providing extension point to create custom installers, have been removed. ## Requirements changes eZ Platform now requires using PHP 7.3. For full list of, see [eZ Platform requirements](https://doc.ibexa.co/en/latest/getting_started/requirements). > **Note: Note** > > Some OS-es, such as Ubuntu 10.x or CentoOS 8.x come with PHP 7.2. In such cases remember to manually update the PHP version. ## Updating For the upgrade details, see [eZ Platform v3.0 project update instructions](https://doc.ibexa.co/en/latest/updating/updating). ## Full changelog | eZ Platform | eZ Enterprise | | -------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | [List of changes for final of eZ Platform v3.0.0 on GitHub](https://github.com/ezsystems/ezplatform/releases/tag/v3.0.0) | [List of changes for final for eZ Platform Enterprise Edition v3.0.0 on GitHub](https://github.com/ezsystems/ezplatform-ee/releases/tag/v3.0.0) | | [List of changes for rc1 of eZ Platform v3.0.0 on GitHub](https://github.com/ezsystems/ezplatform/releases/tag/v3.0.0-rc1) | [List of changes for rc1 for eZ Platform Enterprise Edition v3.0.0 on GitHub](https://github.com/ezsystems/ezplatform-ee/releases/tag/v3.0.0-rc1) | ## eZ Platform v3.0.2 ### Sort Trash items Public PHP API `SortClause` has been exposed for `TrashService` queries: `eZ\Publish\API\Repository\Values\Content\Query\SortClause\Trash\DateTrashed` (to be used by `\eZ\Publish\API\Repository\TrashService::findTrashItems` only). It enables you to sort Trash items by date. # eZ Platform v3.0 deprecations and backwards compatibility breaks This page lists backwards compatibility breaks and deprecations introduced in eZ Platform v3.0. > **Tip: Upgrade to v3** > > For a guide on moving your project to v3, see [eZ Platform v3.0 project update instructions](https://doc.ibexa.co/en/latest/updating/updating). ## Symfony 5 v3.0 now uses Symfony 5 instead of Symfony 3. Refer to [Symfony changelog for 4.0](https://github.com/symfony/symfony/blob/master/CHANGELOG-4.0.md), [for 5.0](https://github.com/symfony/symfony/blob/master/CHANGELOG-5.0.md), [Symfony upgrade guides for 4.0](https://github.com/symfony/symfony/blob/master/UPGRADE-4.0.md), and [for 5.0](https://github.com/symfony/symfony/blob/master/UPGRADE-5.0.md) to learn about all changes it entails. See [v3.0 project update](https://doc.ibexa.co/en/latest/update_and_migration/from_2.5/adapt_code_to_v3/index.md) for the steps you need to take to update your project to Symfony 5. See also [full requirements for installing eZ Platform](https://doc.ibexa.co/en/latest/getting_started/requirements). ### Template configuration Following the upgrade to Symfony 5, [the templating component integration is now deprecated](https://symfony.com/blog/new-in-symfony-4-3-deprecated-the-templating-component-integration). As a result, the way to indicate a template path has changed. Example 1: - Now: `"@@EzPlatformUser/user_settings/list.html.twig"` - Formerly: `"EzPlatformUserBundle:user_settings:list.html.twig"` Example 2: - Now: `{% extends "@EzPublishCore/content_fields.html.twig" %}` - Formerly: `{% extends "EzPublishCoreBundle::content_fields.html.twig" %}` ### Clustering configuration Following the upgrade to Symfony 5, the DFS IO handler must be configured in a different way. For more information, see the Doctrine connection configuration example in the [Clustering](https://doc.ibexa.co/en/latest/guide/clustering/#configuring-the-dfs-io-handler) article. ## Field types The following tags used to register field type features in the [service container](https://doc.ibexa.co/en/latest/api/public_php_api/#service-container) have been renamed: | Former name | New name | | ---------------------------------------------------- | -------------------------------------------------------- | | `ezpublish.fieldType` | `ezplatform.field_type` | | `ezpublish.fieldType.indexable` | `ezplatform.field_type.indexable` | | `ezpublish.storageEngine.legacy.converter` | `ezplatform.field_type.legacy_storage.converter` | | `ezpublish.fieldType.parameterProvider` | `ezplatform.field_type.parameter_provider` | | `ezpublish_rest.field_type_processor` | `ibexa.rest.field_type.processor` | | `ez.fieldFormMapper.value` | `ezplatform.field_type.form_mapper.value` | | `ez.fieldFormMapper.definition` | `ezplatform.field_type.form_mapper.definition` | | `ezpublish.fieldType.externalStorageHandler` | `ezplatform.field_type.external_storage_handler` | | `ezpublish.fieldType.externalStorageHandler.gateway` | `ezplatform.field_type.external_storage_handler.gateway` | Deprecated method `eZ\Publish\SPI\FieldType\FieldType::getName` is now supported with a new signature similar to `eZ\Publish\SPI\FieldType\Nameable::getFieldName()`, which has been removed. For more information, see [eZ Platform v3.0 project update](https://doc.ibexa.co/en/latest/updating/4_3_upgrade_field_types). The deprecated `eZ\Publish\Core\FieldType\RichText` namespace has been removed, as it was moved to a separate bundle in v2.4. The following classes and namespaces have been deprecated and dropped: - `eZ\Publish\SPI\FieldType\EventListener` - `eZ\Publish\SPI\FieldType\Event` - `eZ\Publish\SPI\FieldType\Events\**` Deprecated `ezprice` and `ezpage` field types have been removed. ## Configuration through `ezplatform` In YAML configuration, `ezplatform` is now used instead of `ezpublish` as the main configuration key. ## Assetic support Assetic support has been dropped. ## Installers ### Custom Installers The following Symfony Service definitions that provide extension point to create custom installers have been removed: - `ezplatform.installer.clean_installer` - `ezplatform.installer.db_based_installer` ### Enterprise Edition installer The `ezstudio.installer.studio_installer` service has been renamed to the FQCN-named service `EzSystems\EzPlatformEnterpriseEditionInstallerBundle\Installer\Installer`. Deprecated `ezplatform.ee.installer.class` [service container](https://doc.ibexa.co/en/latest/api/public_php_api/#service-container) parameter has been removed. See [eZ Platform v3.0 project update instructions](https://doc.ibexa.co/en/latest/updating/4_8_upgrade_rest/#custom-installers) for upgrade details. ## ezplatform-admin-ui ### Functions renamed | Former name | New name | | --------------------------------------------- | ------------------------------------------------ | | `ez_is_field_empty` | `ez_field_is_empty` | | `ezplatform_admin_ui_component_group` | `ez_render_component_group` | | `ez_platform_tabs` | `ez_render_tab_group` | | `ez_render_fielddefinition_edit` | `ez_render_field_definition_edit` | | `ez_path_string_to_locations` | `ez_path_to_locations` | | `ez_image_asset_content_field_identifier` | `ez_content_field_identifier_image_asset` | | `encode_field` | `ez_field_encode` | | `ez_http_tag_location` | `ez_http_cache_tag_location` | | `ez_first_filled_image_field_identifier` | `ez_content_field_identifier_first_filled_image` | | `ez_render_fielddefinition_settings` | `ez_render_field_definition_settings` | | `encode_block_value` | `ez_block_value_encode` | | `ezplatform_page_builder_cross_origin_helper` | `ez_page_builder_cross_origin_helper` | ### Twig helper renamed Selected Twig helpers names have been changed. Additionally, the `ez_trans_prop` Twig function has been removed. ### Global variables renamed | Former name | New name | | ----------------- | -------------------- | | `admin_ui_config` | `ez_admin_ui_config` | | `ezpublish` | `ezplatform` | ### Filters renamed | Former name | New name | | ------------------------ | --------------------------- | | `richtext_to_html5` | `ez_richtext_to_html5` | | `richtext_to_html5_edit` | `ez_richtext_to_html5_edit` | ### JavaScript #### Event names changed Selected event names have been changed. | Former name | New name | | ---------------------------------------------------------------- | -------------------------------------------------------------------- | | `invalidFileSize` | `ez-invalid-file-size` | | `addressNotFound` | `ez-address-not-found` | | `cancelErrors` | `ez-cancel-errors` | | `ezsettings.default.content_type.about` | `ezsettings.admin_group.content_type.about` | | `ezsettings.default.content_type.article` | `ezsettings.admin_group.content_type.article` | | `ezsettings.default.content_type.blog` | `ezsettings.admin_group.content_type.blog` | | `ezsettings.default.content_type.blog_post` | `ezsettings.admin_group.content_type.blog_post` | | `ezsettings.default.content_type.folder` | `ezsettings.admin_group.content_type.folder` | | `ezsettings.default.content_type.form` | `ezsettings.admin_group.content_type.form` | | `ezsettings.default.content_type.place` | `ezsettings.admin_group.content_type.place` | | `ezsettings.default.content_type.product` | `ezsettings.admin_group.content_type.product` | | `ezsettings.default.content_type.field` | `ezsettings.admin_group.content_type.field` | | `ezsettings.default.content_type.user` | `ezsettings.admin_group.content_type.user` | | `ezsettings.default.content_type.user_group` | `ezsettings.admin_group.content_type.user_group` | | `ezsettings.default.content_type.file` | `ezsettings.admin_group.content_type.file` | | `ezsettings.default.content_type.gallery` | `ezsettings.admin_group.content_type.gallery` | | `ezsettings.default.content_type.image` | `ezsettings.admin_group.content_type.image` | | `ezsettings.default.content_type.video` | `ezsettings.admin_group.content_type.video` | | `ezsettings.default.content_type.landing_page` | `ezsettings.admin_group.content_type.landing_page` | | `ezsettings.default.content_type.default-config` | `ezsettings.admin_group.content_type.default-config` | | `ezsettings.default.pagination.search_limit` | `ezsettings.admin_group.pagination.search_limit` | | `ezsettings.default.pagination.trash_limit` | `ezsettings.admin_group.pagination.trash_limit` | | `ezsettings.default.pagination.section_limit` | `ezsettings.admin_group.pagination.section_limit` | | `ezsettings.default.pagination.language_limit` | `ezsettings.admin_group.pagination.language_limit` | | `ezsettings.default.pagination.role_limit` | `ezsettings.admin_group.pagination.role_limit` | | `ezsettings.default.pagination.content_type_group_limit` | `ezsettings.admin_group.pagination.content_type_group_limit` | | `ezsettings.default.pagination.content_type_limit` | `ezsettings.admin_group.pagination.content_type_limit` | | `ezsettings.default.pagination.role_assignment_limit` | `ezsettings.admin_group.pagination.role_assignment_limit` | | `ezsettings.default.pagination.policy_limit` | `ezsettings.admin_group.pagination.policy_limit` | | `ezsettings.default.pagination.version_draft_limit` | `ezsettings.admin_group.pagination.version_draft_limit` | | `ezsettings.default.pagination.content_system_url_limit` | `ezsettings.admin_group.pagination.content_system_url_limit` | | `ezsettings.default.pagination.content_custom_url_limit` | `ezsettings.admin_group.pagination.content_custom_url_limit` | | `ezsettings.default.pagination.content_role_limit` | `ezsettings.admin_group.pagination.content_role_limit` | | `ezsettings.default.pagination.content_policy_limit` | `ezsettings.admin_group.pagination.content_policy_limit` | | `ezsettings.default.pagination.bookmark_limit` | `ezsettings.admin_group.pagination.bookmark_limit` | | `ezsettings.default.pagination.notification_limit` | `ezsettings.admin_group.pagination.notification_limit` | | `ezsettings.default.pagination.content_draft_limit` | `ezsettings.admin_group.pagination.content_draft_limit` | | `ezsettings.default.security.token_interval_spec` | `ezsettings.admin_group.security.token_interval_spec` | | `ezsettings.default.user_content_type_identifier` | `ezsettings.admin_group.user_content_type_identifier` | | `ezsettings.default.user_group_content_type_identifier` | `ezsettings.admin_group.user_group_content_type_identifier` | | `ezsettings.default.subtree_operations.copy_subtree.limit` | `ezsettings.admin_group.subtree_operations.copy_subtree.limit` | | `ezsettings.default.notifications.error.timeout` | `ezsettings.admin_group.notifications.error.timeout` | | `ezsettings.default.notifications.warning.timeout` | `ezsettings.admin_group.notifications.warning.timeout` | | `ezsettings.default.notifications.success.timeout` | `ezsettings.admin_group.notifications.success.timeout` | | `ezsettings.default.notifications.info.timeout` | `ezsettings.admin_group.notifications.info.timeout` | | `ezsettings.default.content_tree_module.load_more_limit` | `ezsettings.admin_group.content_tree_module.load_more_limit` | | `ezsettings.default.content_tree_module.children_load_max_limit` | `ezsettings.admin_group.content_tree_module.children_load_max_limit` | | `ezsettings.default.content_tree_module.tree_max_depth` | `ezsettings.admin_group.content_tree_module.tree_max_depth` | | `ezsettings.default.content_tree_module.allowed_content_types` | `ezsettings.admin_group.content_tree_module.allowed_content_types` | | `ezsettings.default.content_tree_module.ignored_content_types` | `ezsettings.admin_group.content_tree_module.ignored_content_types` | | `ezsettings.default.content_tree_module.tree_root_location_id` | `ezsettings.admin_group.content_tree_module.tree_root_location_id` | ### Template organization #### Templates renamed The following templates used in the back office have been renamed: | Former name | New name | | ----------------------------------------------------- | ----------------------------------------------------- | | admin/systeminfo/composer.html.twig | admin/system_info/composer.html.twig | | admin/systeminfo/database.html.twig | admin/system_info/database.html.twig | | admin/systeminfo/hardware.html.twig | admin/system_info/hardware.html.twig | | admin/systeminfo/info.html.twig | admin/system_info/info.html.twig | | admin/systeminfo/php.html.twig | admin/system_info/php.html.twig | | admin/systeminfo/symfony_kernel.html.twig | admin/system_info/symfony_kernel.html.twig | | content/content_edit/parts/javascripts.html.twig | content/content_edit/part/javascripts.html.twig | | content/content_edit/parts/stylesheets.html.twig | content/content_edit/part/stylesheets.html.twig | | content/locationview.html.twig | content/location_view.html.twig | | content/widgets/content_create.html.twig | content/widget/content_create.html.twig | | content/widgets/content_edit.html.twig | content/widget/content_edit.html.twig | | content/widgets/user_edit.html.twig | content/widget/user_edit.html.twig | | errors/403.html.twig | error/403.html.twig | | errors/404.html.twig | error/404.html.twig | | errors/error.html.twig | error/error.html.twig | | fieldtypes/edit/binary_base.html.twig | field_type/edit/binary_base.html.twig | | fieldtypes/edit/binary_base_fields.html.twig | field_type/edit/binary_base_fields.html.twig | | fieldtypes/edit/ezauthor.html.twig | field_type/edit/ezauthor.html.twig | | fieldtypes/edit/ezbinaryfile.html.twig | field_type/edit/ezbinaryfile.html.twig | | fieldtypes/edit/ezboolean.html.twig | field_type/edit/ezboolean.html.twig | | fieldtypes/edit/ezdate.html.twig | field_type/edit/ezdate.html.twig | | fieldtypes/edit/ezdatetime.html.twig | field_type/edit/ezdatetime.html.twig | | fieldtypes/edit/ezgmaplocation.html.twig | field_type/edit/ezgmaplocation.html.twig | | fieldtypes/edit/ezimage.html.twig | field_type/edit/ezimage.html.twig | | fieldtypes/edit/ezimageasset.html.twig | field_type/edit/ezimageasset.html.twig | | fieldtypes/edit/ezkeyword.html.twig | field_type/edit/ezkeyword.html.twig | | fieldtypes/edit/ezmedia.html.twig | field_type/edit/ezmedia.html.twig | | fieldtypes/edit/ezobjectrelation.html.twig | field_type/edit/ezobjectrelation.html.twig | | fieldtypes/edit/ezobjectrelationlist.html.twig | field_type/edit/ezobjectrelationlist.html.twig | | fieldtypes/edit/ezrichtext.html.twig | field_type/edit/ezrichtext.html.twig | | fieldtypes/edit/ezselection.html.twig | field_type/edit/ezselection.html.twig | | fieldtypes/edit/eztime.html.twig | field_type/edit/eztime.html.twig | | fieldtypes/edit/ezuser.html.twig | field_type/edit/ezuser.html.twig | | fieldtypes/edit/relation_base.html.twig | field_type/edit/relation_base.html.twig | | fieldtypes/preview/content_fields.html.twig | field_type/preview/content_fields.html.twig | | fieldtypes/preview/ezimageasset.html.twig | field_type/preview/ezimageasset.html.twig | | fieldtypes/preview/ezobjectrelationlist_row.html.twig | field_type/preview/ezobjectrelationlist_row.html.twig | | Limitation/null_limitation_values.html.twig | limitation/null_limitation_values.html.twig | | Limitation/udw_limitation_value.html.twig | limitation/udw_limitation_value.html.twig | | Limitation/udw_limitation_value_list_item.html.twig | limitation/udw_limitation_value_list_item.html.twig | | parts/breadcrumbs.html.twig | part/breadcrumbs.html.twig | | parts/form/assign_section_widget.html.twig | part/form/assign_section_widget.html.twig | | parts/form/flat_widgets.html.twig | part/form/flat_widgets.html.twig | | parts/location_bookmark.html.twig | part/location_bookmark.html.twig | | parts/menu/sidebar_base.html.twig | part/menu/sidebar_base.html.twig | | parts/menu/sidebar_right.html.twig | part/menu/sidebar_right.html.twig | | parts/menu/sidebar_left.html.twig | part/menu/sidebar_left.html.twig | | parts/menu/top_menu.html.twig | part/menu/top_menu.html.twig | | parts/menu/top_menu_2nd_level.html.twig | part/menu/top_menu_2nd_level.html.twig | | parts/menu/top_menu_base.html.twig | part/menu/top_menu_base.html.twig | | parts/menu/user_menu.html.twig | part/menu/user_menu.html.twig | | parts/navigation.html.twig | part/navigation.html.twig | | parts/notification.html.twig | part/notification.html.twig | | parts/page_title.html.twig | part/page_title.html.twig | | parts/path.html.twig | part/path.html.twig | | parts/tab/content_type.html.twig | part/tab/content_type.html.twig | | parts/tab/default.html.twig | part/tab/default.html.twig | | parts/tab/locationview.html.twig | part/tab/location_view.html.twig | | parts/tab/system_info.html.twig | part/tab/system_info.html.twig | | parts/table_header.html.twig | part/table_header.html.twig | | parts/tag.html.twig | part/tag.html.twig | | Security/base.html.twig | security/base.html.twig | | Security/forgot_user_password/index.html.twig | security/forgot_user_password/index.html.twig | | Security/forgot_user_password/success.html.twig | security/forgot_user_password/success.html.twig | | Security/forgot_user_password/with_login.html.twig | security/forgot_user_password/with_login.html.twig | | Security/form_fields.html.twig | security/form_fields.html.twig | | Security/login.html.twig | security/login.html.twig | | Security/mail/forgot_user_password.html.twig | security/mail/forgot_user_password.html.twig | | Security/reset_user_password/index.html.twig | security/reset_user_password/index.html.twig | | Security/reset_user_password/invalid_link.html.twig | security/reset_user_password/invalid_link.html.twig | | Security/reset_user_password/success.html.twig | security/reset_user_password/success.html.twig | | user-profile/change_user_password.html.twig | user_profile/change_user_password.html.twig | | user-profile/form_fields.html.twig | user_profile/form_fields.html.twig | #### Templates relocated The `@ezdesign/account/error/credentials_expired.html.twig` has been relocated from `src/bundle/Resources/views/Security/error` to `src/bundle/Resources/views/themes/admin/account/error`. ### Universal Discovery Widget The UDW configuration has been changed. For the full list of UDW configuration keys and their descriptions, see [UDW configuration](https://doc.ibexa.co/en/latest/extending/extending_udw/#configuration). ### Online Editor All Online Editor front-end code and assets (such as JS, CSS, or fonts) have been moved from `ezplatform-admin-ui` to `ezplatform-richtext`. ### Adding new tabs in the back office The way of adding custom tab groups in the back office has changed. You now need to [make use of the `TabsComponent`](https://doc.ibexa.co/en/latest/extending/extending_tabs/#adding-a-new-tab-group). ### Content type forms Content type editing, including Action Dispatchers, Form Processors, Types and Data classes related to content types/Limitations, has been moved to `ezplatform-admin-ui` from `repository-forms`. ### Code cleanup in back office The following deprecated items have been removed: | Removed code | Belongs to | Use instead | | ------------------------- | ---------------------------------------------------------------------------------------------------------------- | ---------------------------- | | `canEdit` | `EzSystems\EzPlatformAdminUiBundle\Controller\LanguageController::viewAction` | `can_administrate` | | `canAssign` | `EzSystems\EzPlatformAdminUiBundle\Controller\LanguageController::viewAction` | `can_administrate` | | `baseLanguage` | `EzSystems\EzPlatformAdminUi\EventListener\ContentTranslateViewFilterParametersListener::onFilterViewParameters` | `base_language` | | `contentType` | `EzSystems\EzPlatformAdminUi\EventListener\ContentTranslateViewFilterParametersListener::onFilterViewParameters` | `content_type` | | `isPublished` | `EzSystems\EzPlatformAdminUi\EventListener\ContentTranslateViewFilterParametersListener::onFilterViewParameters` | `ContentInfo::isPublished` | | `fieldDefinitionsByGroup` | `EzSystems\EzPlatformAdminUi\Tab\LocationView\ContentTab` | `field_definitions_by_group` | | `full` | `window.eZ.adminUiConfig.dateFormat` | `fullDateTime` | | `short` | `window.eZ.adminUiConfig.dateFormat` | `shortDateTime` | | `limit` | `EzSystems\EzPlatformAdminUi\UI\Module\Subitems\ContentViewParameterSupplier` | - | | `contentTypeNames` | `window.eZ.adminUiConfig` | `contentTypes` | Following the upgrade to Symfony 5, the following event classes have been deprecated: | Deprecated | Use instead | | ----------------------------------------------------------------- | --------------------------------------------------- | | `Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent` | `Symfony\Component\HttpKernel\Event\ExceptionEvent` | | `Symfony\Component\HttpKernel\Event\GetResponseEvent` | `Symfony\Component\HttpKernel\Event\RequestEvent` | Also, as of Symfony 5, the `transchoice` Twig filter has been replaced with `trans`. New translation strings are required. ##### SubtreeQuery Deprecated `SubtreeQuery` class has been removed. In v3.0, it was replaced by `EzSystems\EzPlatformAdminUi\QueryType\SubtreeQueryType`. ### Permission Choice Loaders The following choiceLoaders classes deprecated in v2.5 have been removed: - `EzSystems\EzPlatformAdminUi\Form\Type\ChoiceList\Loader\PermissionAwareContentTypeChoiceLoader` - `EzSystems\EzPlatformAdminUi\Form\Type\ChoiceList\Loader\PermissionAwareLanguageChoiceLoader` Instead, use the following classes: - `EzSystems\EzPlatformAdminUi\Form\Type\ChoiceList\Loader\ContentCreateContentTypeChoiceLoader` - `EzSystems\EzPlatformAdminUi\Form\Type\ChoiceList\Loader\ContentCreateLanguageChoiceLoader` ### Universal Discovery Widget The deprecated `universal_discovery_widget_module.default_location_id` setting has been replaced with `universal_discovery_widget_module.configuration.default.starting_location_id`. ## ezplatform-admin-ui-modules This package is deprecated. Its code has been moved to [`ezplatform-admin-ui`](#ezplatform-admin-ui). ## ezplatform-content-forms This new package contains forms for content creation moved from `repository-forms`. ## ezplatform-design-engine ### Code cleanup in Design Engine - The deprecated `Twig\Loader\ExistsLoaderInterface` has been removed. - The deprecated `Twig_Profiler_Profile` Twig class has been replaced with `Twig\Profiler\Profile`. - The deprecated `Twig_Environment` Twig class has been replaced with `Twig\Environment` ## ezplatform-form-builder ### JavaScript #### Event names changed The following event names have been changed: | Former name | New name | | ----------------------- | -------------------------- | | `openUdw` | `ez-open-udw` | | `updateFieldName` | `ez-update-field-name` | | `fbFormBuilderLoaded` | `ez-form-builder-loaded` | | `fbFormBuilderUnloaded` | `ez-form-builder-unloaded` | ## ezplatform-http-cache ### FOS Cache Bundle v2 HTTP cache bundle now uses FOS Cache Bundle v2. This entails that: - `EzSystems\PlatformHttpCacheBundle\Proxy\TagAwareStore` has been removed. - `EzSystems\PlatformHttpCacheBundle\Handler\TagHandler` has been changed so that the tag is now provided as an option in `header_formatter`. - `tagResponse()` from `tagHandler` has been replaced by `tagSymfonyResponse()`. - Deprecated `EzSystems\PlatformHttpCacheBundle\Handler\TagHandlerInterface` has been removed. - `EzSystems\PlatformHttpCacheBundle\PurgeClient\PurgeClientInterface` now only accepts an array as argument in the `purge()` method, instead of an int. - The `X-User-Hash` header for recognizing user context has been changed to `X-User-Context-Hash`. - The `key` header for purging tags has been changed to `xkey-softpurge`. - The `PURGE` method has been changed to `PURGEKEY`. - The `ezplatform.http_cache.tags.header` parameter has been removed. Configuration now relies on FOS Cache configuration and its default values. ### Code cleanup in HTTP Cache Instances of the following deprecated event classes have been replaced: | Deprecated class | Replaced with | | ------------------------------------------------------------------------ | --------------------------------------------------- | | `Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent` | `Symfony\Component\HttpKernel\Event\ExceptionEvent` | | `Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent` | `Symfony\Component\HttpKernel\Event\ViewEvent` | | `Symfony\Component\HttpKernel\Event\FilterResponseEvent` | `Symfony\Component\HttpKernel\Event\ResponseEvent` | | `Symfony\Component\HttpKernel\Event\GetResponseEvent` | `Symfony\Component\HttpKernel\Event\RequestEvent` | | `Twig_Extension` | `Twig\Extension\AbstractExtension` | | `Twig_SimpleFunction` | `Twig\TwigFunction` | Selected deprecated Role Service and permission-related methods have been removed. For details, see [code cleanup in kernel](#code-cleanup-in-ez-platform-kernel). ## ezplatform-kernel replacing ezpublish-kernel ### ezplatform-kernel package eZ Platform now makes use of [`ezplatform-kernel`](https://github.com/ezsystems/ezplatform-kernel) instead of `ezpublish-kernel`. This change is introduced without BC breaks. ### API methods Following API methods have been removed: - `\eZ\Publish\API\Repository\ContentService::removeTranslation` - `\eZ\Publish\API\Repository\UserService::loadAnonymousUser` - `\eZ\Publish\API\Repository\Repository::getCurrentUser` - `\eZ\Publish\API\Repository\Repository::getCurrentUserReference` - `\eZ\Publish\API\Repository\Repository::setCurrentUser` - `\eZ\Publish\API\Repository\Repository::hasAccess` - `\eZ\Publish\API\Repository\Repository::canUser` - `\eZ\Publish\API\Repository\RoleService::updateRole` - `\eZ\Publish\API\Repository\RoleService::addPolicy` - `\eZ\Publish\API\Repository\RoleService::deletePolicy` - `\eZ\Publish\API\Repository\RoleService::updatePolicy` - `\eZ\Publish\API\Repository\RoleService::loadPoliciesByUserId` - `\eZ\Publish\API\Repository\RoleService::unassignRoleFromUser` - `\eZ\Publish\API\Repository\RoleService::unassignRoleFromUserGroup` - `\eZ\Publish\API\Repository\Values\Content\Query\Criterion::createFromQueryBuilder` - `\eZ\Publish\API\Repository\Values\Content\Query\Criterion\Ancestor::createFromQueryBuilder` - `\eZ\Publish\API\Repository\Values\Content\Query\Criterion\ContentId::createFromQueryBuilder` - `\eZ\Publish\API\Repository\Values\Content\Query\Criterion\ContentTypeGroupId::createFromQueryBuilder` - `\eZ\Publish\API\Repository\Values\Content\Query\Criterion\ContentTypeId::createFromQueryBuilder` - `\eZ\Publish\API\Repository\Values\Content\Query\Criterion\ContentTypeIdentifier::createFromQueryBuilder` - `\eZ\Publish\API\Repository\Values\Content\Query\Criterion\FieldRelation::createFromQueryBuilder` - `\eZ\Publish\API\Repository\Values\Content\Query\Criterion\FullText::createFromQueryBuilder` - `\eZ\Publish\API\Repository\Values\Content\Query\Criterion\LanguageCode::createFromQueryBuilder` - `\eZ\Publish\API\Repository\Values\Content\Query\Criterion\LocationId::createFromQueryBuilder` - `\eZ\Publish\API\Repository\Values\Content\Query\Criterion\LocationRemoteId::createFromQueryBuilder` - `\eZ\Publish\API\Repository\Values\Content\Query\Criterion\MatchAll::createFromQueryBuilder` - `\eZ\Publish\API\Repository\Values\Content\Query\Criterion\MatchNone::createFromQueryBuilder` - `\eZ\Publish\API\Repository\Values\Content\Query\Criterion\MoreLikeThis::createFromQueryBuilder` - `\eZ\Publish\API\Repository\Values\Content\Query\Criterion\ObjectStateId::createFromQueryBuilder` - `\eZ\Publish\API\Repository\Values\Content\Query\Criterion\ParentLocationId::createFromQueryBuilder` - `\eZ\Publish\API\Repository\Values\Content\Query\Criterion\RemoteId::createFromQueryBuilder` - `\eZ\Publish\API\Repository\Values\Content\Query\Criterion\SectionId::createFromQueryBuilder` - `\eZ\Publish\API\Repository\Values\Content\Query\Criterion\Subtree::createFromQueryBuilder` - `\eZ\Publish\API\Repository\Values\Content\Query\Criterion\Visibility::createFromQueryBuilder` ### SPI methods Following SPI methods have been removed: - `\eZ\Publish\SPI\Persistence\Content\Handler::removeTranslationFromContent` ### Dynamic settings Using dynamic settings (through `$setting$`) and getting settings from the [ConfigResolver](https://doc.ibexa.co/en/latest/guide/config_dynamic/#configresolver) in a class constructor or method call has been dropped. You should use the ConfigResolver instead. Don't store the values globally. Every time the value is needed call `ConfigResolverInterface::getParameter`. ### Controllers #### AbstractController The `eZ\Bundle\EzPublishCoreBundle\Controller` now extends `Symfony\Bundle\FrameworkBundle\Controller\AbstractController` instead of `Symfony\Bundle\FrameworkBundle\Controller\Controller` which has limited access to the [service container](https://doc.ibexa.co/en/latest/api/public_php_api/#service-container). For details, see [Service Subscribers Locators](https://symfony.com/doc/7.4/service_container/service_subscribers_locators.html). The `Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand` is deprecated, use `Symfony\Component\Console\Command\Command` instead. #### ViewController Deprecated `viewLocation` and `embedLocation` actions of the `ViewController` have been removed, along with related route `_ezpublishLocation`. Use: - `viewAction` instead of `viewLocation` - `embedAction` instead of `embedLocation` ### Elasticsearch Experimental, deprecated and unsupported code for Elasticsearch 1.4.2 has been dropped from kernel, to be replaced with a dedicated bundle for the latest Elastic version in the future. ### Field types #### Star Rating The unused `ezsrrating` field type has been removed along with the related database storage and clean installation entries. #### RichText The `ezrichtext` field type has been removed from `ezplatform-kernel`. Use [`ezplatform-richtext`](https://github.com/ezsystems/ezplatform-richtext) instead. Following this change: - The `eZ\Publish\Core\FieldType\RichText` namespace has been dropped. All classes are available in `ezplatform-richtext`. - The only correct configuration (recommended as of v2.4) looks the following way: Now (as of v3.0): ``` ezrichtext: ``` Formerly (deprecated as of v2.4, removed as of v3.0) ``` ezpublish: ezrichtext ``` #### Tags Deprecated `ezpublish.query_type` tag has been removed in favour of `ezplatform.query_type` tag. ### Signal Slots Signal Slots have been replaced by [Symfony Events and Event Listeners](https://symfony.com/doc/7.4/event_dispatcher.html). The application triggers two Events per operation: one before and one after the relevant thing happens (see for example [Bookmark events](https://github.com/ezsystems/ezplatform-kernel/blob/v1.0.0/eZ/Publish/Core/Event/BookmarkService.php)). ### Legacy Storage Gateways The following deprecated (since v6.11) Legacy Storage Gateways have been removed: - `eZ\Publish\Core\FieldType\BinaryFile\BinaryBaseStorage\Gateway\LegacyStorage` - `eZ\Publish\Core\FieldType\BinaryFile\BinaryFileStorage\Gateway\LegacyStorage` - `eZ\Publish\Core\FieldType\MapLocation\MapLocationStorage\Gateway\LegacyStorage` - `eZ\Publish\Core\FieldType\Image\ImageStorage\Gateway\LegacyStorage` - `eZ\Publish\Core\FieldType\Keyword\KeywordStorage\Gateway\LegacyStorage` - `eZ\Publish\Core\FieldType\Media\MediaStorage\Gateway\LegacyStorage` - `eZ\Publish\Core\FieldType\Url\UrlStorage\Gateway\LegacyStorage` - `eZ\Publish\Core\FieldType\User\UserStorage\Gateway\LegacyStorage` Use `DoctrineStorage` Gateways from the same namespace instead. The removed classes refer to External Storage for core field types only. ### REST server Transfer of REST code from kernel to a separate package results in the following change: - The `eZ\Publish\Core\REST` and `eZ\Publish\Core\REST\Common\` namespaces have been replaced by `EzSystems\EzPlatformRest`. - REST client has been dropped. ### SiteAccess-aware Repository The Repository now uses the SiteAccess-aware layer by default. This means Repository objects are now loaded in the translation corresponding to the SiteAccess. To load an object with all its translations, explicitly pass `eZ\Publish\API\Repository\Values\Content\Language::ALL` as the prioritized languages list. ### SiteAccess matching When matching SiteAccesses using custom services, the SiteAccess matcher service must be now tagged with `ezplatform.siteaccess.matcher`. ### Search Indexers Service Provider abstracts for Search Engine Indexer implementations (`\eZ\Publish\Core\Search\Common\IncrementalIndexer` and `\eZ\Publish\Core\Search\Common\Indexer`) now accept `\Doctrine\DBAL\Connection $connection` instead of `\eZ\Publish\Core\Persistence\Database\DatabaseHandler $databaseHandler`. Inject them via `@ezpublish.persistence.connection`. The methods `getContentLocationIds` and `logWarning` of `\eZ\Publish\Core\Search\Common\Indexer` have been dropped. Use Location SPI Persistence Handler in place of `getContentLocationIds`. Use Logger directly in place of `logWarning`. ### Database The following obsolete tables have been removed from the database schema: Removed database tables - ezapprove_items - ezbasket - ezcollab_group - ezcollab_item - ezcollab_item_group_link - ezcollab_item_message_link - ezcollab_item_participant_link - ezcollab_item_status - ezcollab_notification_rule - ezcollab_profile - ezcollab_simple_message - ezcomment - ezcomment_notification - ezcomment_subscriber - ezcomment_subscription - ezcontentbrowserecent - ezcurrencydata - ezdiscountrule - ezdiscountsubrule - ezdiscountsubrule_value - ezenumobjectvalue - ezenumvalue - ezforgot_password - ezgeneral_digest_user_settings - ezinfocollection - ezinfocollection_attribute - ezisbn_group - ezisbn_group_range - ezisbn_registrant_range - ezm_block - ezm_pool - ezmessage - ezmodule_run - ezmultipricedata - eznotificationcollection - eznotificationcollection_item - eznotificationevent - ezoperation_memento - ezorder - ezorder_item - ezorder_nr_incr - ezorder_status - ezorder_status_history - ezpaymentobject - ezpdf_export - ezpending_actions - ezprest_authcode - ezprest_authorized_clients - ezprest_clients - ezprest_token - ezproductcategory - ezproductcollection - ezproductcollection_item - ezproductcollection_item_opt - ezpublishingqueueprocesses - ezrss_export - ezrss_export_item - ezrss_import - ezscheduled_script - ezsearch_search_phrase - ezsession - ezsubtree_notification_rule - eztipafriend_counter - eztipafriend_request - eztrigger - ezuservisit - ezuser_discountrule - ezvatrule - ezvatrule_product_category - ezvattype - ezview_counter - ezwaituntildatevalue - ezwishlist - ezworkflow - ezworkflow_assign - ezworkflow_event - ezworkflow_group - ezworkflow_group_link - ezworkflow_process You can drop unused tables from your database by executing: ``` DROP TABLE ; ``` - The "Setup" folder and Section have been removed from clean installation data. - The "Design" Section has been removed from clean installation data. - The `ezkeyword_attribute_link` table now has a `version` column. #### Content type Update handlers The following obsolete handler has been removed: - `DeferredLegacy` content type Update handler (`eZ\Publish\Core\Persistence\Legacy\Content\Type\Update\Handler\DeferredLegacy`) with its optional Symfony Container Service (`ezpublish.persistence.legacy.content_type.update_handler.deferred`) Subscribe to eZ Platform Symfony Events to handle deferring of updating of content items after their content type update instead. ### Symfony Services The `date_based_publisher.permission_resolver` Symfony Service deprecated in v2.5 has been removed. Instead, you can inject `eZ\Publish\API\Repository\PermissionResolver` and rely on auto-wiring. ### Symfony MIME component The deprecated `Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesserInterface` has been replaced with `Symfony\Component\Mime\MimeTypesInterface`. ### Symfony service container The deprecated Symfony [service container](https://doc.ibexa.co/en/latest/api/public_php_api/#service-container) parameters ending with `.class` have been removed, services relying on them now have their classes defined explicitly. To properly decorate a Symfony service, use the `decorates` attribute instead. For the full list of the dropped parameters, see [kernel documentation](https://github.com/ezsystems/ezpublish-kernel/blob/master/doc/bc/1.0/dropped-container-parameters.md). ### Template parameter names The SiteAccess-aware `pagelayout` setting is deprecated in favor of `page_layout`. View parameter `pagelayout` set by `pagelayout` setting is deprecated in favor of `page_layout`. ### Code cleanup in eZ Platform Kernel Instances of the deprecated code have been replaced: | Deprecated | Replaced with | | ------------------------------------------------------------------------ | ---------------------------------------------------- | | `Symfony\Component\Security\Core\User\AdvancedUserInterface` | `Symfony\Component\Security\Core\User\UserInterface` | | `Symfony\Component\HttpKernel\Event\FilterResponseEvent` | `Symfony\Component\HttpKernel\Event\ResponseEvent` | | `Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent` | `Symfony\Component\HttpKernel\Event\ViewEvent` | | `Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent` | `Symfony\Component\HttpKernel\Event\ExceptionEvent` | | `Symfony\Component\HttpKernel\Event\GetResponseEvent` | `Symfony\Component\HttpKernel\Event\RequestEvent` | | `Symfony\Component\HttpKernel\Event\FilterControllerArgumentsEvent` | `Symfony\Component\HttpKernel\Event\ControllerEvent` | Also, as of Symfony 5, the `transchoice` Twig filter has been replaced with `trans`. New translation strings are required. The deprecated `eZ\Publish\Core\MVC\Symfony\Matcher\MatcherInterface` interface has been dropped. The following deprecated classes relying on that interface have been removed as well: - `eZ\Publish\Core\MVC\Symfony\Matcher\AbstractMatcherFactory` - `eZ\Publish\Core\MVC\Symfony\Matcher\ContentBasedMatcherFactory` - `eZ\Publish\Core\MVC\Symfony\Matcher\ContentMatcherFactory` - `eZ\Publish\Core\MVC\Symfony\Matcher\LocationMatcherFactory` ### Twig classes The following deprecated Twig classes have been replaced: | Deprecated | Replaced with | | -------------------------------- | ------------------------------- | | `Twig_Extensions_Extension_Intl` | `Twig\Extensions\IntlExtension` | | `Twig_Template` | `Twig\Template` | | `Twig_Node` | `Twig\Node\Node` | ### Twig intl extension Twig intl extension [has been dropped](https://github.com/twigphp/Twig-extensions/blob/master/README.rst). ### EzPublishMigration The `EzPublishMigration` bundle has been dropped. ### EzMigrationBundle As of v3.3.3, the `ezsystems/EzMigrationBundle` bundle has been dropped. Use `ibexa/migrations` instead. ### Password hashes Insecure password hash types deprecated since v1.13 have been removed: - `PASSWORD_HASH_MD5_PASSWORD` - `PASSWORD_HASH_MD5_USER` - `PASSWORD_HASH_MD5_SITE` - `PASSWORD_HASH_PLAINTEXT` Login with the removed hashes doesn't longer work. Users can use the "Forgot password" feature to request a new, valid password. ### Strict types for PHP API Strict types have been added to public PHP API methods. ### Zeta Components (eZc) Database handler The deprecated Zeta Components (eZc) Database handler has been dropped. All classes and interfaces from `eZ\Publish\Core\Persistence\Database` and `eZ\Publish\Core\Persistence\Doctrine` namespaces have been removed. `ezpublish.connection` has been removed. Use `ezpublish.persistence.connection` instead. The signature of the `\eZ\Publish\Core\Persistence\Legacy\URL\Query\CriterionHandler::handle` contract now accepts `\Doctrine\DBAL\Query\QueryBuilder` instead of `\eZ\Publish\Core\Persistence\Database\SelectQuery` and has the following form: ``` use \Doctrine\DBAL\Query\QueryBuilder; use \eZ\Publish\Core\Persistence\Legacy\URL\Query\CriteriaConverter; use \eZ\Publish\API\Repository\Values\URL\Query\Criterion; public function handle(CriteriaConverter $converter, QueryBuilder $query, Criterion $criterion); ``` `ezpublish.api.search_engine.legacy.dbhandler` and `ezpublish.api.storage_engine.legacy.dbhandler` have been removed. Inject `\Doctrine\DBAL\Connection` via `ezpublish.persistence.connection` instead. #### Field type External Storage Handlers The field type External Storage Handlers `$context` arrays no longer have the "connection" key. You should rely on injected Connection instead. The `$context` array of `\eZ\Publish\SPI\FieldType\FieldStorage` methods (`storeFieldData`, `getFieldData`, `deleteFieldData`, `getIndexData`) is deprecated and will be dropped in the next major version. You should rely on injected Connection instead. ## ezplatform-page-builder #### JavaScript #### Event names changed The following event names have been changed: | Former name | New name | | ------------------------- | ------------------------------- | | `openUdw` | `ez-open-udw` | | `openAirtimePopup` | `ez-open-airtime-popup` | | `postUpdateBlocksPreview` | `ez-post-update-blocks-preview` | | `pbIframeLoaded` | `ez-page-builder-iframe-loaded` | | `pbHideTools` | `ez-page-builder-hide-tools` | Additionally, the listener for `pbPreviewReloaded` has been removed. ## ezplatform-page-fieldtype ### Namespace location update The following namespaces have been changed: | Namespace | Former location | New location | | ------------------------------- | ----------------------------------------- | ------------------------------------------------ | | `FieldData` | `EzSystems\RepositoryForms\Data\Content\` | `EzSystems\EzPlatformContentForms\Data\Content\` | | `FieldValueFormMapperInterface` | `EzSystems\RepositoryForms\FieldType\` | `EzSystems\EzPlatformContentForms\FieldType\` | ## ezplatform-rest ### Code cleanup in eZ Platform REST Selected deprecated Role Service and permission-related methods have been removed. For details, see [code cleanup in kernel](#code-cleanup-in-ez-platform-kernel). Using the Criteria element in REST input query (search view) payload has been deprecated since eZ Platform v1.6 and was dropped in this release. ## ezplatform-richtext ### Code cleanup in eZ Platform RichText Selected deprecated permission-related methods have been removed. For details, see [code cleanup in kernel](#code-cleanup-in-ez-platform-kernel). ### Input and output converters Following the removal of the `ezrichtext` field type from kernel, the following deprecated converter tags have been changed: | Formerly | Currently | | ---------------------------------------------- | ------------------------------------ | | `ezpublish.ezrichtext.converter.output.xhtml5` | `ezrichtext.converter.output.xhtml5` | | `ezpublish.ezrichtext.converter.input.xhtml5` | `ezrichtext.converter.input.xhtml5` | ### Online Editor Configuration providers exposing the following JavaScript variables have been dropped: - `eZ.adminUiConfig.alloyEditor` replaced by `eZ.richText.alloyEditor` - `eZ.adminUiConfig.richTextCustomTags` replaced by `eZ.richText.customTags` - `eZ.adminUiConfig.richTextCustomStyles` replaced by `eZ.richText.customStyles` The following Webpack Encore entries have been changed: - `ezplatform-admin-ui-alloyeditor-css` replaced by `ezplatform-richtext-onlineeditor-css` - `ezplatform-admin-ui-alloyeditor-js` replaced by `ezplatform-richtext-onlineeditor-js` All Online Editor front-end code and assets (such as JS, CSS, or fonts) have been moved from `ezplatform-admin-ui` to `ezplatform-richtext`. #### Custom button configuration Configuring custom Online Editor buttons with `ezrichtext.alloy_editor.extra_buttons` is deprecated. Use [`ezplatform.system..fieldtypes.ezrichtext.toolbars..buttons`](https://doc.ibexa.co/en/latest/extending/online_editor_button) instead. ### View matching When matching views using custom services, the services must be now tagged with `ezplatform.view.matcher`. The matching must be configured in the following way: ``` content_view: full: folder: template: folder.html.twig match: '@App\Matcher\MyMatcher': ~ ``` ### Service tags The following `ezrichtext` service tags have been extended to be consistent with other service tags: | Currently | Formerly | | ------------------------------------------------ | ------------------------------------- | | `ezplatform.ezrichtext.converter.output.xhtml5` | `ezrichtext.converter.output.xhtml5` | | `ezplatform.ezrichtext.converter.input.xhtml5` | `ezrichtext.converter.input.xhtml5` | | `ezplatform.ezrichtext.validator.input.ezxhtml5` | `ezrichtext.validator.input.ezxhtml5` | ## ezplatform-solr-search-engine The `ezplatform:solr_create_index` command has been removed. Use `ezplatform:reindex` instead. ## ezplatform-user ### User settings As a result of moving user settings to the [`ezplatform-user`](https://github.com/ezsystems/ezplatform-user) package, the following deprecated code for handling the settings has been dropped: - `EzSystems\EzPlatformAdminUi\UserSetting\` - `EzSystems\EzPlatformAdminUi\Pagination\Pagerfanta\UserSettingsAdapter` - `EzSystems\EzPlatformAdminUi\Form\Type\User\Setting\UserSettingUpdateType` - `EzSystems\EzPlatformAdminUiBundle\Controller\UserProfile\UserPasswordChangeController` - `EzSystems\EzPlatformAdminUiBundle\Controller\User\{UserSettingsController,UserForgotPasswordController}` ### Code cleanup in eZ Platform User The deprecated `Symfony\Bundle\FrameworkBundle\Controller\Controller` has been replaced with `Symfony\Bundle\FrameworkBundle\Controller\AbstractController`. ## flex-workflow This package is deprecated. Its functionality has been moved to `ezplatform-workflow`. ## repository-forms Forms located in `repository-forms` have been moved to other packages. Content type editing, including Action Dispatchers, Form Processors, Types and Data classes related to content types/Limitations, has been moved to `ezplatform-admin-ui`. The following locations have been changed: | Former location | New location | | ------------------------------------------------------------------------ | -------------------------------------------------------------------------- | | `EzSystems\RepositoryForms\Data\FieldDefinitionData` | `EzSystems\EzPlatformAdminUi\Form\Data\FieldDefinitionData` | | `EzSystems\RepositoryForms\FieldType\FieldDefinitionFormMapperInterface` | `EzSystems\EzPlatformAdminUi\FieldType\FieldDefinitionFormMapperInterface` | | `EzSystems\RepositoryForms\Limitation\LimitationFormMapperInterface` | `EzSystems\EzPlatformAdminUi\Limitation\LimitationFormMapperInterface` | | `EzSystems\RepositoryForms\Limitation\LimitationValueMapperInterface` | `EzSystems\EzPlatformAdminUi\Limitation\LimitationValueMapperInterface` | Forms for content creation have been moved to a new `ezplatform-content-forms` package. `repository-forms` remains as an additional layer ensuring that your custom implementations that use the package still work. To use this repository, you have to add the package manually to your `composer.json`. ## eZ Platform v3.0.2 ### ezplatform-admin-ui v3.0.2 The following classes have been moved to `EzPlatformContentFormsBundle`: | Former location | Current location | | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | | `EzSystems\EzPlatformAdminUi\Form\Data\User\UserPasswordChangeData` | `EzSystems\EzPlatformUser\Form\Data\UserPasswordChangeData` | | `EzSystems\EzPlatformAdminUi\Form\Data\User\UserPasswordForgotData` | `EzSystems\EzPlatformUser\Form\Data\UserPasswordForgotData` | | `EzSystems\EzPlatformAdminUi\Form\Data\User\UserPasswordResetData` | `EzSystems\EzPlatformUser\Form\Data\UserPasswordResetData` | | `EzSystems\EzPlatformAdminUi\Form\Type\User\UserPasswordChangeType` | `EzSystems\EzPlatformUser\Form\Type\UserPasswordChangeType` | | `EzSystems\EzPlatformAdminUi\Form\Type\User\UserPasswordForgotType` | `EzSystems\EzPlatformUser\Form\Type\UserPasswordForgotType` | | `EzSystems\EzPlatformAdminUi\Form\Type\User\UserPasswordForgotWithLoginType` | `EzSystems\EzPlatformUser\Form\Type\UserPasswordForgotWithLoginType` | | `EzSystems\EzPlatformAdminUi\Form\Type\User\UserPasswordResetType` | `EzSystems\EzPlatformUser\Form\Type\UserPasswordResetType` | | `EzSystems\EzPlatformAdminUi\Validator\Constraints\Password` | `EzSystems\EzPlatformUser\Validator\Constraints\Password` | | `EzSystems\EzPlatformAdminUi\Validator\ConstraintsPasswordValidator` | `EzSystems\EzPlatformUser\Validator\Constraints\PasswordValidator` | | `EzSystems\EzPlatformAdminUi\Validator\Constraints\UserPassword` | `EzSystems\EzPlatformUser\Validator\Constraints\UserPassword` | | `EzSystems\EzPlatformAdminUi\Validator\Constraints\UserPasswordValidator` | `EzSystems\EzPlatformUser\Validator\Constraints\UserPasswordValidator\ValidationErrorsProcessor` | The following methods have been moved to `EzPlatformUserBundle`: | Former method | Current method | | -------------------------------------------------------------------------- | ----------------------------------------------------------------------- | | `EzSystems\EzPlatformAdminUi\Form\Factory\FormFactory::changeUserPassword` | `EzSystems\EzPlatformUser\Form\Factory\FormFactory::changeUserPassword` | | `EzSystems\EzPlatformAdminUi\Form\Factory\FormFactory::forgotUserPassword` | `EzSystems\EzPlatformUser\Form\Factory\FormFactory::forgotUserPassword` | | `EzSystems\EzPlatformAdminUi\Form\Factory\FormFactory::resetUserPassword` | `EzSystems\EzPlatformUser\Form\Factory\FormFactory::resetUserPassword` | | `EzSystems\EzPlatformAdminUi\Form\Factory\FormFactory::updateUserSetting` | `EzSystems\EzPlatformUser\Form\Factory\FormFactory::updateUserSetting` | The following classes have been moved to `EzPlatformContentFormsBundle`. | Former location | Current location | | ---------------------------------------------------------------------- | --------------------------------------------------------------------------- | | `EzSystems\EzPlatformAdminUi\Validator\ValidationErrorsProcessor` | `EzSystems\EzPlatformContentForms\Validator\ValidationErrorsProcessor` | | `EzSystems\EzPlatformAdminUi\Validator\Constraints\FieldTypeValidator` | `EzSystems\EzPlatformContentForms\Validator\Constraints\FieldTypeValidator` | # eZ Platform v2.5 **Version number**: v2.5 **Release date**: March 29, 2019 **Release type**: Long Term Supported ## Notable changes ### Content tree You can now navigate through your website with a content tree. It allows you to easily browse your content in the back office. Each content item has a unique icon that helps you identify it without opening. *[Image: Content tree in the menu]* For more information on custom configuration, see [content tree](https://doc.ibexa.co/en/2.5/guide/config_back_office/#content-tree) in Developer Documentation. For full description of the interface, see [content tree](https://doc.ibexa.co/projects/userguide/en/2.5/content_model/#content-tree) in User Documentation. ### Webpack Encore This release introduces [Webpack Encore](https://symfony.com/doc/7.4/frontend.html#webpack-encore) as the preferred tool for asset management. This leads to [changes in requirements](#requirements-changes). Assetic is still in use, but it will be deprecated in a future version. ### PostgreSQL This release enables you to [use PostgreSQL](https://doc.ibexa.co/en/2.5/guide/databases/#using-postgresql) for database instead of the default MySQL. Database schema is now created based on [YAML configuration](https://github.com/ezsystems/ezpublish-kernel/blob/master/eZ/Bundle/EzPublishCoreBundle/Resources/config/storage/legacy/schema.yaml). ### GraphQL You can now take advantage of [GraphQL](https://doc.ibexa.co/en/2.5/api/graphql) to query and operate on content. It uses a domain schema based on your content model. For more information, see [GraphQL documentation](https://graphql.org/). ### Matrix field type The new [Matrix field type](https://doc.ibexa.co/en/2.5/api/field_types_reference/matrixfield) enables you to store a table of data. Columns in the matrix are defined in the field definition. *[Image: Configuring a Matrix field type]* #### Migration of legacy XML format You can now migrate your content from legacy XML format to a new `ezmatrix` value with the following command: ``` bin/console ezplatform:migrate:legacy_matrix ``` ### User bundle The new [ezplatform-user](https://github.com/ezsystems/ezplatform-user) bundle now centralizes all features related to user management, such as user accounts, registering, or changing passwords. > **Dxp: Dxp** > > ### Workflow improvements > > You can now preview a diagram of the configured workflows in the **Admin** panel. > > *[Image: Diagram of a workflow configuration]* > > After selecting configured workflow administrator, the user is now able to see all content items under review for it. > > *[Image: Content under review]* ### Online editor improvements #### Anchors in Rich Text field You can now link fragments of text by adding Anchors in Rich Text fields. #### Inline custom tags You can now create [inline custom tags](https://doc.ibexa.co/en/2.5/guide/extending/extending_online_editor/#inline-custom-tags) in Rich Text fields. #### Custom CK Editor plugins You can now easily use [custom CK Editor plugins](https://doc.ibexa.co/en/2.5/guide/extending/extending_online_editor/#plugins-configuration) in AlloyEditor. ### Hiding and revealing content You can now hide and reveal content items from the back office. Hidden content is unavailable on the front page regardless of permissions or [Location visibility](https://doc.ibexa.co/en/2.5/guide/content_management/#location-visibility). *[Image: Icon for hiding content]* ### Product version preview The Dashboard now shows the version of eZ Platform you're running. *[Image: eZ Platform version]* ### Expanded User Settings The User Settings menu has been expanded with the following options: - Preferred language of the back office - Preferred date format - Option to enable or disable a character counter for Rich Text fields *[Image: User settings screen with new settings]* ### Various back office improvements This release introduced several back office improvements to facilitate editorial experience, including: - [Icons for content types and the ability to define them](https://doc.ibexa.co/en/2.5/guide/extending/extending_back_office/#custom-content-type-icons) - Ability to collapse and expand content preview to have easier access to the Sub-items list - Responsive Sub-items table with selectable column layout - Simpler assigning of object states to content *[Image: Back office improvements]* ### Permissions #### `Content/Create` policy for users You can now define a 'Content/Create' policy for a user or a user group. It enables or disables (if not set) the **Create** button in your dashboard. #### Universal Discovery Widget `allowed_content_types` can now limit selection in UDW search and browse sections to specified content types. *[Image: Create button in Dashboard]* ### API improvements New API improvements include: - `sudo()` exposed officially in API to make it more clear how you can skip permission checks when needed - `AssignSectionToSubtreeSignal` to assign Sections to subtrees - new `loadLanguageListByCode()` and `loadLanguageListById()` endpoints for bulk loading of languages - new method `ContentService->loadContentInfoList()` for bulk loading Content information - it can be used with `ContentService->loadContentListByContentInfo()` to bulk load Content - v2.5 also takes advantage of it in, for example, `RelationList` and `ParameterProvider` - now Persistence cache layer also caches selected metadata objects in-memory - indexation of related objects in the full text search ## Requirements changes Due to using Webpack Encore, you now need [Node.js and yarn](https://doc.ibexa.co/en/2.5/updating/updating) to install or update eZ Platform. This release also changes support for versions of the following third-party software: - Solr 4 is no longer supported. Use Solr 6 instead (Solr 6.6LTS recommended). - Apache 2.2 is no longer supported. Use Apache 2.4 instead. - Varnish 4 is no longer supported. Use Varnish 5.1 or higher (6.0LTS recommended). For full list of supported versions, see [Requirements](https://doc.ibexa.co/en/2.5/getting_started/requirements). ### Password requirements This version introduces stricter default password quality requirements. Passwords must be at least 10 characters long, and must include upper and lower case letters, and digits. Existing passwords aren't changed. See [backwards compatibility changes](https://github.com/ezsystems/ezpublish-kernel/blob/7.5/doc/bc/changes-7.5.md) for detailed information. ## Full changelog | eZ Platform | eZ Enterprise | | --------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | | [eZ Platform v2.5.0](https://github.com/ezsystems/ezplatform/releases/tag/v2.5.0) | [eZ Enterprise v2.5.0](https://github.com/ezsystems/ezplatform-ee/releases/tag/v2.5.0) | | [eZ Platform v2.5.0-rc2](https://github.com/ezsystems/ezplatform/releases/tag/v2.5.0-rc2) | [eZ Enterprise v2.5.0-rc2](https://github.com/ezsystems/ezplatform-ee/releases/tag/v2.5.0-rc2) | | [eZ Platform v2.5.0-rc1](https://github.com/ezsystems/ezplatform/releases/tag/v2.5.0-rc1) | [eZ Enterprise v2.5.0-rc1](https://github.com/ezsystems/ezplatform-ee/releases/tag/v2.5.0-rc1) | | [eZ Platform v2.5.0-beta2](https://github.com/ezsystems/ezplatform/releases/tag/v2.5.0-beta2) | [eZ Enterprise v2.5.0-beta2](https://github.com/ezsystems/ezplatform-ee/releases/tag/v2.5.0-beta2) | | [eZ Platform v2.5.0-beta1](https://github.com/ezsystems/ezplatform/releases/tag/v2.5.0-beta1) | [eZ Enterprise v2.5.0-beta1](https://github.com/ezsystems/ezplatform-ee/releases/tag/v2.5.0-beta1) | ## eZ Platform v2.5.2 ### Updating The `leafo/scssphp` package had to be replaced by `scssphp/scssphp` due to maintainability. If you use classes from the `Leafo\ScssPhp` namespace, change them to `ScssPhp\ScssPhp`. ### SolrCloud You can now take advantage of [SolrCloud in eZ Platform Solr search engine](https://doc.ibexa.co/en/2.5/guide/search/solr/#solrcloud). It enables you to set up a cluster of Solr servers for highly available and fault tolerant environment. ### Online Editor #### Custom attributes It's now possible to add [custom data attributes and CSS classes](https://doc.ibexa.co/en/2.5/guide/extending/extending_online_editor/#custom-data-attributes-and-classes) to elements in the Online Editor. #### Translatable custom tag choice attributes You can now translate labels of choice attributes in Custom tags using the `ezrichtext.custom_tags..attributes..choices..label` configuration key. ### URL Wildcards [URL wildcards](https://doc.ibexa.co/en/2.5/guide/url_management/#url-wildcards) enable you to set up global URL redirections. ## eZ Platform v2.5.3 ### API improvements `SectionService::loadSection` has been improved to return a filtered list when user doesn't have access to a Section, instead of throwing an exception. ## eZ Platform v2.5.4 ### Permission improvements `RoleService` methods have been improved to return a filtered list when user doesn't have access to content, instead of throwing an exception. The following methods are affected: - `RoleService::loadRoles` - `RoleService::getRoleAssignmentsForUser` - `RoleService::getRoleAssignmentsForUserGroup` `content/cleantrash` policy now allows the user to empty the trash even if they would not have access to the trashed content. ### Docker environment BCMath PHP extension has been added to the Docker environments to enable the Allure reporting tool. ### Deprecated features This section provides a list of deprecated features to be removed in eZ Platform v3.0. #### Custom Installers - The `\EzSystems\PlatformInstallerBundle\Installer\CleanInstaller` class and its [service container](https://doc.ibexa.co/en/2.5/api/service_container) definition (`ezplatform.installer.clean_installer`) have been deprecated in favor of `EzSystems\PlatformInstallerBundle\Installer\CoreInstaller` which requires the [Doctrine Schema Bundle](https://github.com/ezsystems/doctrine-dbal-schema) to be enabled. - The `ezplatform.installer.db_based_installer` service container definition has been deprecated in favor of its FQCN-named equivalent (`EzSystems\PlatformInstallerBundle\Installer\DbBasedInstaller`). - `vendor/ezsystems/ezpublish-kernel/data/mysql/schema.sql` has been deprecated and isn't used by the installation process anymore. ## eZ Platform v2.5.6 ### Configuration through `ezplatform` In YAML configuration, you can now use `ezplatform` and `ezpublish` as the main configuration key. ### API improvements The following PHP API methods have been added: - `ContentService::countContentDrafts` returns the number of all drafts for the provided user - `ContentService::loadContentDraftList` returns a list of all drafts for the provided user - `ContentService::countReverseRelations` returns the number of all reverse relations for a content item - `ContentService::loadReverseRelationList` returns a list of all reverse relations for a content item ### Solr 7.7 With v2.5.6 you can optionally use Solr 7.7. To enable it: 1. Update the `ezplatform-solr-search-engine` package version to ~2.0. 2. Follow [Solr upgrade documentation](https://lucene.apache.org/solr/guide/7_7/solr-upgrade-notes.html). 3. Reindex your content. 4. Clear cache. ## eZ Platform v2.5.9 ### Search result improvements When searching in the back office you can now select languages to filter results through. ### Searchable Matrix field The Matrix field isn't fully searchable. # eZ Platform v2.4 **Version number**: v2.4 **Release date**: December 21, 2018 **Release type**: Fast Track ## Notable changes > **Dxp: Dxp** > > ### Editorial workflow > > [Editorial Workflow](https://doc.ibexa.co/en/2.5/guide/workflow) enables you to pass content through a series of stages. > > Each step can be used to represent for example contributions and approval of different teams and editors. For instance, an article can pass through draft, design and proofreading stages. > > The workflow mechanism is [permission-aware](https://doc.ibexa.co/en/2.5/guide/workflow/#permissions). You can limit access to content in different workflow stages, or the ability to pass content through specific transitions. > > *[Image: Workflow event timeline]* > > Workflow Engine is located in the [ezplatform-workflow bundle](https://github.com/ezsystems/ezplatform-workflow). ### RichText #### RichText field type RichText field type has been extracted to a separate bundle, [ezsystems/ezplatform-richtext](https://github.com/ezsystems/ezplatform-richtext). Relying on any class from the `eZ\Publish\Core\FieldType\RichText` namespace is deprecated. If you're implementing any interface or extending any base class from the old namespace, refer to its PHPDoc to see what to implement or extend instead. Make sure to enable the new eZ Platform RichTextBundle. See [RichText field type Reference](https://doc.ibexa.co/en/2.5/api/field_types_reference/richtextfield). #### RichText block In the Page Builder you can make use of the RichText block. It enables you to insert text created using the Online Editor with all features of a RichText Field. *[Image: RichText block]* #### Improved styling in Online Editor Online Editor has been improved with new styling. *[Image: Online Editor menu]* #### Images in RichText You can now attach links to images in the Online Editor: *[Image: Adding a link to an image in Online Editor]* #### Formatted text in RichText You can now use formatted text in RichText Fields (provided by means of a `literal` tag). *[Image: Formatted Text in Online Editor]* #### Inline embedding in RichText The new `embed-inline` built-in view type enables embedding content items within a block element in RichText. #### Custom tag - `ezcontent` The `ezcontent` property is now editable in the UI and can be used to store the output/preview of a custom tag. To learn how it works, see [FactBox tag](https://doc.ibexa.co/en/2.5/guide/extending/extending_online_editor/#example-factbox-tag). ### Content type translation You can now translate content type names and Field definitions. This possibility is available automatically when you have the target language configured (in the same way as for translating content, see [Languages](https://doc.ibexa.co/en/2.5/guide/internationalization)). *[Image: Content type with existing translations]* When you translate Content of this type, the content type information is displayed in the new language. *[Image: Editing a content translation with translated Field names]* ### Multi-file management New multi-file content management functionalities enable you to move and delete multiple files at the same time. For more information, see[Multi-file content management](https://doc.ibexa.co/projects/userguide/en/2.5/multi_file_content_management/#multi-file-content-management). > **Dxp: Dxp** > > ### Forms > > #### Create form on the fly > > You can now create Forms on the fly from the Universal Discovery Widget. > > *[Image: Creating a Form on the Fly]* > > #### Embedding forms in Pages > > You can use the new Form block to embed an existing form on a Page. ### Draft list The list of all drafts can now be found in the **Administrator User** menu under **Drafts**. *[Image: Administrator User list of all Drafts]* For more information, see [Reviewing a draft](https://doc.ibexa.co/projects/userguide/en/2.5/publishing/flex_workflow/#reviewing-a-draft). ### Subtree search filter A new filter enables you to filter search results by Subtree. For more information, see [Simplified Filtered search](https://doc.ibexa.co/projects/userguide/en/2.5/search/#simplified-filtered-search). ### Sub-items limit You can now set a number of items displayed in the table with the **Sub-items** setting in your User Settings. *[Image: Setting for subitems limit in user preferences]* ### Policy labels update The outdated policy labels are now updated: | Old | New | | ----------- | ---------------------------- | | class | Content type | | ParentClass | Content type of Parent | | node | Location | | parentdepth | Parent Depth | | parentgroup | Content type Group of Parent | | parentowner | Owner of Parent | | subtree | Subtree of Location | *[Image: Updated policy labels]* ### API improvements #### Simplified use of content type objects This release introduces a few simplifications to API use for content types: - Exposes `content->getContentType()` for easier use, including from Twig as `content.contentType`. When iterating over the result set of content/Locations these are effectively loaded all at once. - Adds a possibility to load several content types in bulk with `ContentTypeService->loadContentTypeList()`. - `UserService` now exposes `isUser()` and `isUserGroup()`. They don't need to do a lookup to the database to tell if a content item is of type user or user group. #### Load multiple locations You're now able to load multiple locations at once, with `LocationService->loadLocationList()`. The biggest benefit of this feature is saving load time on complex landing pages when HTTP cache is cold or disabled, including when in development mode. ### BC breaks and important behavior changes - Online Editor format for `ezlink` inside `ezembed` tag changed to an anchor tag. See [ezplatform-richtext/pull/20](https://github.com/ezsystems/ezplatform-richtext/pull/20). - The merge order of content edit forms has been changed. It can affect you if you extended the content edit template. See [ezplatform-admin-ui/pull/720](https://github.com/ezsystems/ezplatform-admin-ui/pull/720). - Changes to the handling of multilingual content types, see [BC notes in the kernel](https://github.com/ezsystems/ezpublish-kernel/blob/7.5/doc/bc/changes-7.4.md). ## Full list of new features, improvements and bug fixes since v2.3 | eZ Platform | eZ Enterprise | | ------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | [List of changes for final of eZ Platform v2.4.0 on GitHub](https://github.com/ezsystems/ezplatform/releases/tag/v2.4.0) | [List of changes for final for eZ Platform Enterprise Edition v2.4.0 on GitHub](https://github.com/ezsystems/ezplatform-ee/releases/tag/v2.4.0) | | [List of changes for rc1 of eZ Platform v2.4.0 on GitHub](https://github.com/ezsystems/ezplatform/releases/tag/v2.4.0-rc1) | [List of changes for rc1 for eZ Platform Enterprise Edition v2.4.0 on GitHub](https://github.com/ezsystems/ezplatform-ee/releases/tag/v2.4.0-rc1) | | [List of changes for beta1 of eZ Platform v2.4.0 on GitHub](https://github.com/ezsystems/ezplatform/releases/tag/v2.4.0-beta1) | [List of changes for beta1 of eZ Platform Enterprise Edition v2.4.0 on GitHub](https://github.com/ezsystems/ezplatform-ee/releases/tag/v2.4.0-beta1) | ## eZ Platform v2.4.2 > **Dxp: Dxp** > > ### Update eZ Enterprise v2.4 to v2.4.2 > > This release brings full support for Map\\Host matcher when SiteAccesses are configured for different domains. > > Token-based authentication (based on JSON Web Token specification) replaced cookie-based authentication that did not work with SiteAccesses configured for a different domains in the Page Builder. Authentication mechanizm is enabled by default in v2.4.2, however, the following steps are required during upgrade from v2.4 to v2.4.2+ Enterprise installation: > > 1. Register `LexikJWTAuthenticationBundle` bundle in `/app/AppKernel.php` > > ``` > public function registerBundles() > { > $bundles = array( > // ... > new Lexik\Bundle\JWTAuthenticationBundle\LexikJWTAuthenticationBundle(), > // Ibexa > // ... > ); > } > ``` > > 2. Add the following configuration to `/app/config/config.yml` > > ``` > lexik_jwt_authentication: > secret_key: '%secret%' > encoder: > signature_algorithm: HS256 > # Disabled by default, because Page Builder uses custom extractor > token_extractors: > authorization_header: > enabled: false > cookie: > enabled: false > query_parameter: > enabled: false > ``` > > By default `HS256` is used as signature algorithm for generated token but we strongly recommend switching to SSH keys. > > For more information, see [`LexikJWTAuthenticationBundle` installation instruction](https://github.com/lexik/LexikJWTAuthenticationBundle/blob/master/Resources/doc/index.md#installation). > > 3. Add `EzSystems\EzPlatformPageBuilder\Security\EditorialMode\TokenAuthenticator` authentication provider to `ezpublish_front` firewall before `form_login` in `app/config/security.yml`: > > ``` > security: > # ... > firewalls: > ezpublish_front: > # ... > simple_preauth: > authenticator: 'EzSystems\EzPlatformPageBuilder\Security\EditorialMode\TokenAuthenticator' > form_login: > require_previous_session: false > # ... > ``` > > 4. Make sure that parameter `page_builder.token_authenticator.enabled` has value `true`. If the parameter isn't present, add it to `/app/config/config.yml`: > > ``` > # ... > parameters: > # ... > page_builder.token_authenticator.enabled: true > ``` # eZ Platform v2.3 **Version number**: v2.3 **Release date**: October 5, 2018 **Release type**: Fast Track ## Notable changes ### Content scheduling > **Note: Note** > > You can now schedule content on a Page to become visible at a specific time in the future. > > To do this you can use the **Schedule** tab in any block's configuration or a special Content Scheduler block. > > In the **Schedule** tab you can define when any block becomes visible and when it disappears from a Page. > > *[Image: Schedule tab]* > > Content Scheduler is a special block with a queue of content items, each with its own airtime. The Content becomes available at the airtime, and is replaced with new content items coming in from the queue. > > *[Image: Content Scheduler]* > > All changes to scheduled content on a Page are visible in the timeline. > > *[Image: Timeline and list of upcoming events]* > > The timeline also shows other events, such a Content published with the date-based publisher. For more information, see [advanced publishing options](https://doc.ibexa.co/projects/userguide/en/2.5/publishing/advanced_publishing_options) in User Documentation. ### Form Builder > **Note: Note** > > The new Form Builder enables you to create Form content items with multiple form fields. > > *[Image: Form Builder]* > > You can preview and download submissions in the back office. > > *[Image: Form Builder submissions]* > > See [Extending Form Builder](https://doc.ibexa.co/en/2.5/guide/extending/extending_form_builder) for information on how to modify and create Form fields. For more information, see [forms](https://doc.ibexa.co/projects/userguide/en/2.5/creating_content_advanced/#forms) in User Documentation. ### ImageAsset field type You can now create a single source media library with images that can be reused across the system. For more information, see [Reusing images](https://doc.ibexa.co/en/2.5/guide/images/#reusing-images) and [ImageAsset field type reference](https://doc.ibexa.co/en/2.5/api/field_types_reference/imageassetfield). *[Image: Set up multiple relations with image]* ### Regenerating URL aliases A new `ezplatform:urls:regenerate-aliases` command enables you to regenerate all URL aliases. You can use it after changing URL alias configuration, or in case of database corruption. For more information, see [Regenerating URL aliases](https://doc.ibexa.co/en/2.5/guide/url_management/#regenerating-url-aliases). ### User preferences You can now access and set user preferences in the user menu. *[Image: User preferences screen with time zone settings]* It's covered by the `user/preferences` policy. ### Dates in preferred timezone eZ Platform can now display dates across the system using timezone from User Settings. ### Improved selection in UDW Selection of content in Universal Discovery Widget has seen improvements, in particular when selecting multiple content items. *[Image: Multiple selection on UDW]* ### API improvements Improvements to the API cover: - [`UserPreferenceService`](https://github.com/ezsystems/ezpublish-kernel/blob/v7.3.0/eZ/Publish/API/Repository/UserPreferenceService.php) - [`ASSET` Relation type](https://github.com/ezsystems/ezpublish-kernel/blob/v7.3.0-rc2/eZ/Publish/Core/REST/Client/Input/Parser/Relation.php#L84) - `TrashItem->trashed` timestamp covers when a content item was placed in Trash #### Back office translations There are three new ways you can now contribute to back office translations: - translate in-context with bookmarks - translate in-context with console - translate directly on the Crowdin website For more information, see [How to translate the interface using Crowdin](https://doc.ibexa.co/en/2.5/community_resources/translations/#how-to-translate-the-interface-using-crowdin). ## Full list of new features, improvements and bug fixes since v2.2.0 | eZ Platform | eZ Enterprise | | ------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | [List of changes for final of eZ Platform v2.3.0 on GitHub](https://github.com/ezsystems/ezplatform/releases/tag/v2.3.0) | [List of changes for final for eZ Platform Enterprise Edition v2.3.0 on GitHub](https://github.com/ezsystems/ezplatform-ee/releases/tag/v2.3.0) | | [List of changes for rc2 of eZ Platform v2.3.0 on GitHub](https://github.com/ezsystems/ezplatform/releases/tag/v2.3.0-rc2) | [List of changes for rc2 for eZ Platform Enterprise Edition v2.3.0 on GitHub](https://github.com/ezsystems/ezplatform-ee/releases/tag/v2.3.0-rc2) | | [List of changes for rc1 of eZ Platform v2.3.0 on GitHub](https://github.com/ezsystems/ezplatform/releases/tag/v2.3.0-rc1) | [List of changes for rc1 for eZ Platform Enterprise Edition v2.3.0 on GitHub](https://github.com/ezsystems/ezplatform-ee/releases/tag/v2.3.0-rc1) | | [List of changes for beta1 of eZ Platform v2.3.0 on GitHub](https://github.com/ezsystems/ezplatform/releases/tag/v2.3.0-beta1) | [List of changes for beta1 of eZ Platform Enterprise Edition v2.3.0 on GitHub](https://github.com/ezsystems/ezplatform-ee/releases/tag/v2.3.0-beta1) | ## Installation [Installation guide](https://doc.ibexa.co/en/2.5/getting_started/install_ez_platform) [Technical requirements](https://doc.ibexa.co/en/2.5/getting_started/requirements) # eZ Platform v2.2.0 **Version number**: v2.2.0 **Release date**: June 29, 2018 **Release type**: Fast Track ## Notable changes ### Page Builder This version introduces the **Page Builder** which replaces the landing page editor from earlier versions. *[Image: Page Builder]* > **Note: Note** > > The Page Builder doesn't offer all blocks that landing page editor did. The removed blocks include Schedule and Form blocks. They will be included again in a future release. > > The Places Page Builder block has been removed from the clean installation and will only be available in the demo out of the box. If you had been using this block in your site, re-apply its configuration based on [the demo](https://github.com/ezsystems/ezplatform-ee-demo/blob/master/app/config/blocks.yaml). #### Modifying the Page content type You can edit the new Page content type by adding Fields and create new content types with the Page field type. *[Image: Editing Fields in Page Builder]* #### Page block design In the Page block config you can now specify the CSS class with its own style for the specific block: *[Image: Setting the styling in Block configuration]* > **Caution: Updating to 2.2** > > Refer to [Updating eZ Platform](https://doc.ibexa.co/en/2.5/updating/5_update_2.2) for a database update script. > > To update to 2.2 with existing Content you need a [dedicated script for converting the landing page into the new Page](https://doc.ibexa.co/en/2.5/updating/5_update_2.2/#migrate-landing-pages). ### Bookmarks Bookmark service allows you to create bookmarks for Locations by selecting a star located next to the content type name as shown in the screenshot below. Each Location can only be bookmarked once, multiple bookmarks on one Location cause an error. *[Image: Bookmark]* You can find the list of all bookmarks in *Browse content* section. There, you can manage bookmarks by deleting them or by checking if specific Location has been bookmarked. ### Image placeholders [Placeholder generator](https://doc.ibexa.co/en/2.5/guide/images/#setting-placeholder-generator) enables you to replace any missing image with downloaded or generated image placeholder. It can be used when you're working on an existing database and you're not able to download uploaded images to your local development environment because of their large size. *[Image: Placeholder GenericProvider]* ### Standard design eZ Platform now comes with two designs that use the [design engine](https://doc.ibexa.co/en/2.5/guide/design_engine): `standard` for content view and `admin` for the back office. For more information, see [default designs](https://doc.ibexa.co/en/2.5/guide/design_engine/#default-designs). > **Caution: Caution** > > If you encounter problems during upgrading, disable the override by setting `ez_platform_standard_design.override_kernel_templates` to `false`. ### Previewing user and user group permissions When viewing user or user group content items you can now preview what permissions are assigned to them. *[Image: Preview of permissions assigned to a User]* You can also [select which content types are treated the same way as user of user group](https://doc.ibexa.co/en/2.5/guide/config_repository/#user-identifiers) for these purposes. ### Change from UTF8 to UTF8MB4 Database charset is changed from UTF8 to UTF8MB4, to support 4-byte characters. > **Caution: Caution** > > To cover this change when upgrading, follow the instructions in the [update guide](https://doc.ibexa.co/en/2.5/updating/5_update_2.2). ### URL generation pattern You can now select the pattern that is used to generate URL patterns. For more information about the available settings, see [URL alias patterns](https://doc.ibexa.co/en/2.5/guide/url_management/#url-alias-patterns). > **Caution: Default URL generation pattern** > > The default URL generation pattern changes from `urlalias` to `urlalias_lowercase`. This change only applies to new Content. Pay attention to the new `url_alias.slug_converter.transformation` setting in the meta-repository when updating your installation. ### Choosing installation types Installation types used with the `ezplatform:install` command are now more consistent: - `ezplatform-clean` - `ezplatform-demo` - `ezplatform-ee-clean` - `ezplatform-ee-demo` You can also use the new `composer ezplatform-install` command which automatically chooses a correct installation type for the given meta-repository. ## API changes ### Notifications [Notification Bundle](https://github.com/ezsystems/ezstudio-notifications) is now moved into CoreBundle of [EzPublishKernel](https://github.com/ezsystems/ezpublish-kernel). This allows whole community to get access to eZ notification system. ### Bookmarks New Bookmark service had been added. Bookmark operations are now available via the REST API. ### Simplified use of Content and languages in API This release introduces a few notable simplifications to API use. Here are some highlights: - [Location object now gives access to Content](https://doc.ibexa.co/en/2.5/api/public_php_api_browsing/#getting-content-from-a-location) - [Optional SiteAccessAware Repository](https://doc.ibexa.co/en/2.5/api/public_php_api_browsing/#siteaccess-aware-repository) ## Full list of new features, improvements and bug fixes since v2.1.0 | eZ Platform | eZ Enterprise | | ------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | [List of changes for final of eZ Platform v2.2.0 on GitHub](https://github.com/ezsystems/ezplatform/releases/tag/v2.2.0) | [List of changes for final for eZ Platform Enterprise Edition v2.2.0 on GitHub](https://github.com/ezsystems/ezplatform-ee/releases/tag/v2.2.0) | | [List of changes for rc1 of eZ Platform v2.2.0 on GitHub](https://github.com/ezsystems/ezplatform/releases/tag/v2.2.0-rc1) | [List of changes for rc1 for eZ Platform Enterprise Edition v2.2.0 on GitHub](https://github.com/ezsystems/ezplatform-ee/releases/tag/v2.2.0-rc1) | | [List of changes for beta1 of eZ Platform v2.2.0 on GitHub](https://github.com/ezsystems/ezplatform/releases/tag/v2.2.0-beta1) | [List of changes for beta1 of eZ Platform Enterprise Edition v2.2.0 on GitHub](https://github.com/ezsystems/ezplatform-ee/releases/tag/v2.2.0-beta1) | ## Installation [Installation guide](https://doc.ibexa.co/en/2.5/getting_started/install_ez_platform) [Technical requirements](https://doc.ibexa.co/en/2.5/getting_started/requirements) # eZ Platform v2.1.0 **Version number**: v2.1.0 **Release date**: March 27, 2018 **Release type**: Fast Track ## Notable changes ### Custom Tags You can now add custom tags to RichText fields. Custom tags enable you to extend the menu of available elements when editing a RichText field with the Online Editor. For more information, see [Custom tags](https://doc.ibexa.co/en/2.5/guide/extending/extending_online_editor/#custom-tags). ### Object states Object states enable you to create sets of custom states and then assign them to Content. *[Image: "Lock" object state]* Object states can be used in conjunction with [permissions](https://doc.ibexa.co/en/2.5/guide/limitation_reference/#state-limitation). ### Content on the fly Content on the fly enables you to create new Content anywhere in the application from the Universal Discovery widget. *[Image: Content on the fly]* ### URL alias management You can now add custom URL aliases to content items from the URL tab. Aliases can be set per language of the content item. *[Image: Custom URL aliases]* ### REST: GET Location that matches URL alias You can now translate URL aliases into Locations with `urlAlias` parameter provided. When user provides parameter in URL, Location with given URL Alias is returned via `GET /content/locations`. ### Password management You can now change your password, or request a new one if you forgot it. *[Image: Password recovery]* > **Caution: Caution** > > The reaction time when requesting a reset of the password varies depending on whether an account with the provided email exists in the database or not. This could be misused to confirm existing email addresses. To avoid this, set Swift Mailer to `spool` mode. ### Simplified filtered search During search you can now filter the results by content type, Section, Modified and Created dates. *[Image: Simplified filtered search]* ### REST: search with FieldCriterion You can now perform REST search via `POST /views` using custom `FieldCriterion`. This allows you to build custom content logic queries with nested logical operators OR/AND/NOT. ### Other UI improvements - When accessing the back office from a link to a specific content item, after logging in you're now redirected to the proper content view. - In edit mode you can now preview content as it looks in any SiteAccess it's available in. - When you start editing a content item that already has an open draft, you can see a draft conflict screen: *[Image: Draft conflict window]* ## Full list of new features, improvements and bug fixes since v2.0.0 | eZ Platform | eZ Enterprise | | ------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | [List of changes for final of eZ Platform v2.1.0 on GitHub](https://github.com/ezsystems/ezplatform/releases/tag/v2.1.0) | [List of changes for final for eZ Platform Enterprise Edition v2.1.0 on GitHub](https://github.com/ezsystems/ezplatform-ee/releases/tag/v2.1.0) | | [List of changes for rc1 of eZ Platform v2.1.0 on GitHub](https://github.com/ezsystems/ezplatform/releases/tag/v2.1.0-rc1) | [List of changes for rc1 for eZ Platform Enterprise Edition v2.1.0 on GitHub](https://github.com/ezsystems/ezplatform-ee/releases/tag/v2.1.0-rc1) | | [List of changes for beta1 of eZ Platform v2.1.0 on GitHub](https://github.com/ezsystems/ezplatform/releases/tag/v2.1.0-beta1) | [List of changes for beta1 of eZ Platform Enterprise Edition v2.1.0 on GitHub](https://github.com/ezsystems/ezplatform-ee/releases/tag/v2.1.0-beta1) | ## Installation [Installation guide](https://doc.ibexa.co/en/2.5/getting_started/install_ez_platform) [Technical requirements](https://doc.ibexa.co/en/2.5/getting_started/requirements) # eZ Platform v2.0.0 **Version number**: v2.0.0 **Release date**: December 22, 2017 **Release type**: Fast Track > **Note: LTS release** > > Parallel to this v2.0.0 version we are releasing a Long Term Support (LTS) version based on 1.x: [v1.13.0](https://doc.ibexa.co/en/latest/release_notes/ez_platform_v1.13.0_lts/index.md). ## Notable changes eZ Platform v2.0.0 introduces significant changes to the architecture, especially to the back-office interface. #### Symfony 3 eZ Platform has become a pure Symfony application, based on Symfony 3, which brings with it many enhancements. > **Note: Note** > > The move to [Symfony 3](https://symfony.com/roadmap?version=3.4) causes some changes, for example to the project's directory structure. > > Among others, the `var` directory now contains cache and logs. The `bin` directory is now used to call the `console` command, so use `bin/console` instead of `app/console`. #### Back-office interface The back-office interface no longer uses YUI, and is instead based on React components and Bootstrap, which makes it easier to extend. Explore the Extending section in the menu to learn how to extend the new version of the UI. The features of eZ Platform remain the same as in 1.x versions. However, the look of the interface has changed significantly. *[Image: v2.0.0 interface]* #### Studio The StudioUI still uses the 1.x interface. It will be rewritten to the new architecture in an upcoming version. ### Changed requirements eZ Platform v2.0.0 requires PHP version 7.1, instead of 5.6, as before. Together with improved architecture, this ensures that the application can work up to several times more quickly than before. ## Installation [Installation guide](https://doc.ibexa.co/en/2.5/getting_started/install_ez_platform) [Technical requirements](https://doc.ibexa.co/en/2.5/getting_started/requirements) # eZ Platform v1.13.0 **The Long Term Support v1.13.0 release of eZ Platform and eZ Platform Enterprise Edition is available as of December 22, 2017.** > **Note: v2 release** > > Parallel to this v1.13.0 LTS version we are releasing a fast-track version in a new architecture: [v2.0.0](https://doc.ibexa.co/en/latest/release_notes/ez_platform_v2.0.0/index.md). ## Notable changes since v1.12.0 ### Link manager The new Link manager enables you to manage all links to external websites that are embedded in the whole site, whether in Rich Text or in URL Field. You can edit a link in the manager and it's updated automatically in all content items. *[Image: Link Manager]* ### Copying subtrees in the back office You can now copy a content item with all of its sub-items in the back office. The maximum number of content items that can be copied this way can be set in configuration, see [Copy subtree limit](https://doc.ibexa.co/en/latest/guide/config_back_office/#copy-subtree-limit). *[Image: Copy subtree option in the menu]* ### REST API improvements - Added a REST endpoint for deleting a translation from all versions of a content item. - Added s a `fieldTypeIdentifier` field to the REST response for Version, which provides the field type. ### ezplatform-http-cache extensibility Made ezplatform-http-cache extensible in third party bundles. ### Fastly You can [serve Varnish through Fastly](https://doc.ibexa.co/en/latest/infrastructure_and_maintenance/cache/http_cache/reverse_proxy/). ## Full list of new features, improvements and bug fixes since v1.12.0 | eZ Platform | eZ Enterprise | | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | [List of changes for final of eZ Platform v1.13.0 on GitHub](https://github.com/ezsystems/ezplatform/releases/tag/v1.13.0) | [List of changes for final for eZ Platform Enterprise Edition v1.13.0 on GitHub](https://github.com/ezsystems/ezplatform-ee/releases/tag/v1.13.0) | | [List of changes for rc1 of eZ Platform v1.13.0 on GitHub](https://github.com/ezsystems/ezplatform/releases/tag/v1.13.0-rc1) | [List of changes for rc1 for eZ Platform Enterprise Edition v1.13.0 on GitHub](https://github.com/ezsystems/ezplatform-ee/releases/tag/v1.13.0-rc1) | | [List of changes for beta2 of eZ Platform v1.13.0 on GitHub](https://github.com/ezsystems/ezplatform/releases/tag/v1.13.0-beta2) | [List of changes for beta2 of eZ Platform Enterprise Edition v1.13.0 on GitHub](https://github.com/ezsystems/ezplatform-ee/releases/tag/v1.13.0-beta2) | ### Installation [Installation Guide](https://doc.ibexa.co/en/latest/getting_started/install_ez_platform) [Technical Requirements](https://doc.ibexa.co/en/latest/getting_started/requirements) ### Download #### eZ Platform - Download at [eZPlatform.com](http://ezplatform.com/#download) #### eZ Enterprise - [Customers: eZ Enterprise subscription (BUL License)](https://support.ez.no/Downloads) - Partners: Test & Trial software access (TTL License) If you would like to become familiar with the products, [request a demo](https://www.ibexa.co/forms/request-a-demo). ### Updating To update the product, follow the [updating guide](https://doc.ibexa.co/en/latest/updating/updating/). # eZ Platform v1.12.0 **The FAST TRACK v1.12.0 release of eZ Platform and eZ Platform Enterprise Edition is available as of October 31, 2017.** If you're looking for the Long Term Support (LTS) release, see ## Notable changes since v1.11.0 #### New Options in the Rich Text editor The Rich Text editor now enables you to add both ordered and unordered lists. You also have new options to format your text using subscript, superscript, quote and strikethrough. *[Image: New text formatting options]* #### Improved full text search capabilities Added support for full-text search query syntax in Solr. #### Deleting translations You can now remove translations from content item Versions through the PHP API. For more information, see the section on [deleting translations](https://doc.ibexa.co/en/latest/api/public_php_api_creating_content/#deleting-a-translation). You also have a new endpoint available for deleting a single Version. #### Improved Security for password storage 1.12 introduces and enables by default more secure user passwords hashing using bcrypt, and is future-proofed for new hashing formats being added to PHP, like Argon2i coming with PHP 7.2. This feature is added both in eZ Platform and the accompanying eZ Publish legacy 2017.10 release for projects looking to migrate to a newer version of Platform and take advantage of the new features. #### Improved Varnish performance This release switches default HTTPCache usage to use ezplatform-http-cache package, which uses Varnish xkey allowing: soft purge, better cache clearing logic and longer ttl. For Varnish users be aware thus change implies new VCL and requirement for varnish-moduels package, see [below](#updating). ## Full list of new features, improvements and bug fixes since v1.11.0 | eZ Platform | eZ Enterprise | | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | [List of changes for final of eZ Platform v1.12.0 on GitHub](https://github.com/ezsystems/ezplatform/releases/tag/v1.12.0) | [List of changes for final for eZ Platform Enterprise Edition v1.12.0 on GitHub](https://github.com/ezsystems/ezplatform-ee/releases/tag/v1.12.0) | | [List of changes for rc1 of eZ Platform v1.12.0 on GitHub](https://github.com/ezsystems/ezplatform/releases/tag/v1.12.0-rc1) | [List of changes for rc1 for eZ Platform Enterprise Edition v1.12.0 on GitHub](https://github.com/ezsystems/ezplatform-ee/releases/tag/v1.12.0-rc1) | | [List of changes for beta2 of eZ Platform v1.12.0 on GitHub](https://github.com/ezsystems/ezplatform/releases/tag/v1.12.0-beta2) | [List of changes for beta2 of eZ Platform Enterprise Edition v1.12.0 on GitHub](https://github.com/ezsystems/ezplatform-ee/releases/tag/v1.12.0-beta2) | ### Installation [Installation Guide](https://doc.ibexa.co/en/latest/getting_started/install_ez_platform) [Technical Requirements](https://doc.ibexa.co/en/latest/getting_started/requirements) ### Download #### eZ Platform - Download at eZPlatform.com #### eZ Enterprise - [Customers: eZ Enterprise subscription (BUL License)](https://support.ez.no/Downloads) - Partners: Test & Trial software access (TTL License) If you would like to become familiar with the products, [request a demo](https://www.ibexa.co/forms/request-a-demo). ### Updating To update to this version, follow the [updating guide](https://doc.ibexa.co/en/latest/updating/updating/). > **Caution: BC: Change for Varnish users** > > This release enables the [ezplatform-http-cache](https://github.com/ezsystems/ezplatform-http-cache) Bundle by default as it has a more future-proof approach for HttpCache: > > - Cache tagging is more reliable at clearing all affected cache on, for instance, subtree operations > - More performant using [xkey](https://github.com/varnish/varnish-modules/blob/master/docs/vmod_xkey.rst) *("Surrogate Key")* and soft purging, over BAN and growing ban list > > This means: > > - There is a new VCL > - Requires Varnish 4.1+ with `varnish-modules` *(incl. xkey)*, or Varnish Plus where it's built in > > For more information, see [doc/varnish/varnish.md](https://github.com/ezsystems/ezplatform/blob/master/doc/varnish/varnish.md). > > #### How to still use the old VCL and the old X-Location-Id headers > > In all 1.x releases you're still able to revert this and use the old deprecated system if you need to. To do that: > > - Keep using the VCL for BAN > - Disable *(comment out)* `EzSystemsPlatformHttpCacheBundle` in `app/AppKernel.php` > - Change `app/AppCache.php` back to extend `eZ\Bundle\EzPublishCoreBundle\HttpCache` > > That's it, other changes added in 1.12 like increased cache ttl and `fos_http_cache` cache control rules for error pages should work also with BAN setup, and are thus optional. > **Note: React** > > This release changes the way of loading React to avoid a case where it was loaded twice and caused errors. Take this into consideration if you user React in your own implementation. > > For more information, see [this PR](https://github.com/ezsystems/PlatformUIBundle/pull/906). # eZ Platform v1.11.0 **The FAST TRACK v1.11.0 release of eZ Platform and eZ Platform Enterprise Edition is available as of August 24, 2017.** If you're looking for the Long Term Support (LTS) release, see ## Notable changes since v1.10.0 ### eZ Platform #### Improved way of writing field type gateways You now have access to the Doctrine connection instead of the Zeta Components Database connection-like object which has been exposed to field types until now. The former way will be removed in a future major version. #### Content type limitation for Relation (single) field You can now specify a content type limitation for the Relation field, just like with the Relation List field. This enables you to limit what kind of relations Editors can select also on singular relation fields. *[Image: Adding a new Relation (single) Field with allowed content types]* This has been made possible by initial legacy contribution from [@peterkeung](https://github.com/peterkeung), and [@slaci](https://github.com/slaci) who ported this feature over to eZ Platform so that both could go in. #### API endpoint for removing translations You can now use an API endpoint to remove a given translation completely from a content item. ### eZ Platform Enterprise Edition #### Collection block New Collection block is available in the landing page editor. It enables you to manually select a set of content items to be displayed. *[Image: Collection block options with three content items selected]* > **Note: Note** > > To enable adding content to a Collection block in a clean installation, you need to configure the views for the block and define which content types can be embedded in it. > > For more information and an example, see [block templates](https://doc.ibexa.co/en/latest/content_management/pages/page_blocks/#block-templates). #### RecommendationBundle adapted for YooChoose v2 In the RecommendationBundle, the id generation of a visitor was changed to use a persistent cookie value instead of a new one each time a visitor arrives at the site. Fetching recommendations was also refactored to use the v2 of the Recommendation API. With this step the *clickrecommended* event now includes detailed feedback information about how recommendations were generated. This is very important for the analysis of statistics to measure the performance of recommendations. #### Official Enterprise Support for Legacy Bridge Starting with this release we are going to officially support an alternative *(and perhaps simpler)* way to gradually migrate from eZ Publish to eZ Platform. From now on, also as an Enterprise user, you can use **Legacy Bridge**. There is a corresponding new eZ Publish legacy release called 2017.08 available for this, for both community and enterprise users. Unlike eZ Publish 5.4LTS, this should be seen as a Fast Track release of legacy: it's tailored for those that want a more modern eZ Platform and Symfony version to take advantage of all new features of the platform and facilitate the migration. More info on this in a separate blog post soon. As with eZ Platform itself, Enterprise users will receive the same full support, maintenance, and priority security patch handling as they're used to for this setup. > **Note: Note** > > Not supported for clean/new installs, intended for use with migrations. The Legacy Bridge integration doesn't have same performance, scalability or integrated experience as pure Platform setup. There are known edge cases where for instance cache or search index cannot always be immediately updated across the two systems using the bridge, which is one of the many reasons why we recommend a pure Platform setup where that is possible. ## Full list of new features, improvements and bug fixes since v1.10.0 | eZ Platform | eZ Enterprise | | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | [List of changes for final of eZ Platform v1.11.0 on GitHub](https://github.com/ezsystems/ezplatform/releases/tag/v1.11.0) | [List of changes for final for eZ Platform Enterprise Edition v1.11.0 on GitHub](https://github.com/ezsystems/ezplatform-ee/releases/tag/v1.11.0) | | [List of changes for rc1 of eZ Platform v1.11.0 on GitHub](https://github.com/ezsystems/ezplatform/releases/tag/v1.11.0-rc1) | [List of changes for rc1 for eZ Platform Enterprise Edition v1.11.0 on GitHub](https://github.com/ezsystems/ezplatform-ee/releases/tag/v1.11.0-rc1) | | [List of changes for beta1 of eZ Platform v1.11.0 on GitHub](https://github.com/ezsystems/ezplatform/releases/tag/v1.11.0-beta1) | [List of changes for beta1 of eZ Platform Enterprise Edition v1.11.0 on GitHub](https://github.com/ezsystems/ezplatform-ee/releases/tag/v1.11.0-beta1) | ### Installation [Installation Guide](https://doc.ibexa.co/en/latest/getting_started/install_ez_platform) [Technical Requirements](https://doc.ibexa.co/en/latest/getting_started/requirements) ### Download #### eZ Platform - Download at [eZPlatform.com](http://ezplatform.com/#download) #### eZ Enterprise - [Customers: eZ Enterprise subscription (BUL License)](https://support.ez.no/Downloads) - Partners: Test & Trial software access (TTL License) If you would like to become familiar with the products, [request a demo](https://www.ibexa.co/forms/request-a-demo). ### Updating To update the product, follow the [updating guide](https://doc.ibexa.co/en/latest/updating/updating/). # eZ Platform v1.10.0 **The FAST TRACK v1.10.0 release of eZ Platform and eZ Platform Enterprise Edition is available as of June 28, 2017.** If you're looking for the Long Term Support (LTS) release, see ## Notable changes since v1.9.0 ### eZ Platform #### Online Editor: Table editing support This release introduces the ability to add tables in the RichText editor, enabling you to list up tabular data using table headings, merged table cells and more. This is a first step. We aim to provide more in terms of table support in the editor later. For the time being images and embedding aren't supported within the table, as you won't be able to move them out or edit them. We also don't provide yet ability to style the table within the editor. #### New Design Engine This is a new way to handle design, theming and design overrides, similar to what we had in eZ Publish. It enables you to define different Themes which are collections of assets and templates. You can then assemble Themes (that can override each other) to define Designs, and eventually, assign a Design to a SiteAccess. This is a powerful concept that we aim to use in our out-of-the-box templates and demo sites. It comes especially handy when using eZ Platform for a multisite installation and willing to reuse design parts. For more information, see [Bundle documentation](https://github.com/ezsystems/ezplatform-design-engine/tree/master/doc). #### API: Simplified usage with translations As part of ongoing effort to simplify everyday aspects of the API for v2, you can now simpler deal with SiteAccess languages and translations. ###### Example For objects such as content, content type, field definitions and more, to get translated name, description or fields you would before this change have to do the following in PHP and Twig: **Typical use of API prior to v1.10:** ``` $content = $this->contentService->loadContent( 42, $this->configResolver->getParameter('languages') ); $name = $this->translationHelper->getTranslatedContentName($content); $field = $this->translationHelper->getTranslatedField($content, 'body'); $value = $field->value; ``` As long as languages are provided to API when retrieving a given object, this can now be simplified to: **As of v1.10:** ``` $content = $this->contentService->loadContent( 42, $this->configResolver->getParameter('languages') ); $name = $content->getName(); $value = $content->getFieldValue('body'); ``` #### SOLR: Index time boosting & Improved Facets support One of the new features in 1.10 *(Solr Bundle 1.4)* is the possibility to [configure index time boosting](https://doc.ibexa.co/en/latest/guide/search/solr/#boost-configuration), which enables you to properly tune the search results to be relevant for your content architecture. In addition to that, we made progress on providing native support for faceted search within eZ Platform when using the Solr Bundle. You can now use facets based on ContentTypes, Sections and Users, see [Performing a Faceted Search](https://doc.ibexa.co/en/latest/api/public_php_api_search/#faceted-search) page for how to use them. We plan to provide more facets natively in the coming releases. #### Cluster migration script EXPERIMENTAL FEATURE Starting with 1.10, a new command `ezplatform:io:migrate-files` has been added, allowing you to migrate files from one storage to another, for instance file system to S3, or S3 to NFS or opposite. For documentation check the [technical feature documentation](https://github.com/ezsystems/ezpublish-kernel/blob/6.7/doc/specifications/io/io_migration_script.md) for now. #### Miscellaneous - Kernel: Don't store full User object in Sessions anymore, just User Id ### eZ Platform Enterprise Edition - Studio - Form deletion is managed more gracefully, including warnings and the option to download collected data before deleting a form *[Image: Deleting a form with data]* - Schedule block logic has been updated and improved. ### eZ Platform Enterprise Edition - Studio Demo - [NovaeZSEOBundle](https://github.com/Novactive/NovaeZSEOBundle/) is now included in Studio Demo. NovaeZSEOBundle includes a new field type that lets you manage your SEO strategy in very advanced and powerful ways. - We also improved the way we provide personalization in the site using a profiling block and letting the end user manage their preferences by themselves. In this new version, the end user, once logged on the site, can access a page where they can define their content preferences. See [here](https://ez.no/Blog/Personalization-Does-Not-Have-to-Be-that-Complex) for more information. ## Full list of new features, improvements and bug fixes since v1.9.0 | eZ Platform | eZ Studio | | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | [List of changes for final of eZ Platform v1.10.0 on GitHub](https://github.com/ezsystems/ezplatform/releases/tag/v1.10.0) | [List of changes for final for eZ Platform Enterprise Edition v1.10.0 on GitHub](https://github.com/ezsystems/ezplatform-ee/releases/tag/v1.10.0) | | [List of changes for rc2 of eZ Platform v1.10.0 on GitHub](https://github.com/ezsystems/ezplatform/releases/tag/v1.10.0-rc2) | [List of changes for rc1 for eZ Platform Enterprise Edition v1.10.0 on GitHub](https://github.com/ezsystems/ezplatform-ee/releases/tag/v1.10.0-rc1) | | [List of changes for beta3 of eZ Platform v1.10.0 on GitHub](https://github.com/ezsystems/ezplatform/releases/tag/v1.10.0-beta3) | [List of changes for beta1 of eZ Platform Enterprise Edition v1.10.0 on GitHub](https://github.com/ezsystems/ezplatform-ee/releases/tag/v1.10.0-beta1) | ### Acknowledgements Kudos to [@emodric](https://twitter.com/emodric) for the Tags bundle, [@pspanja](https://twitter.com/pspanja) for the work Solr index-time boosting, [@plopix](https://twitter.com/Plopix) for the NovaeZSEOBundle, [@jvieilledent](https://twitter.com/jvieilledent) for the initial work on the design engine and to all others who contributed bug reports, feedback and comments that made this release possible. ### Installation [Installation Guide](https://doc.ibexa.co/en/latest/getting_started/install_ez_platform) [Technical Requirements](https://doc.ibexa.co/en/latest/getting_started/requirements) ### Download #### eZ Platform - Download at [eZPlatform.com](http://ezplatform.com/#download) #### eZ Enterprise - [Customers: eZ Enterprise subscription (BUL License)](https://support.ez.no/Downloads) - Partners: Test & Trial software access (TTL License) If you would like to become familiar with the products, [request a demo](https://www.ibexa.co/forms/request-a-demo). # eZ Platform v1.9.0 **The FAST TRACK v1.9.0 release of eZ Platform and eZ Platform Enterprise Edition is available as of April 19, 2017.** If you're looking for the Long Term Support (LTS) release, see ## Notable changes since v1.8.0 ### eZ Platform #### Multifile upload You can now create collections of content quickly: upload multiple files in bulk and they're imported directly into the content repository. The files are automatically imported as content using the content type that matches their MIME type. Go to the content view, drag and drop or select multiple files in the sub-items area and you get direct access for further editing. As ever, this solution can be customized so that you create your own matching rules. #### Content browser In version 1.8 we introduced a new Content Browser in the Universal Discovery Widget (UDW). This Content Browser is now used to browse content everywhere, also when accessing the content tree through the left pane in Platform UI. This allows users to reach the entire repository from this toolbar (which was previously limited in terms of number of items per level), it also provides a much more consistent user experience. To reflect this change, the content tree button has been renamed **Content browse**. #### Miscellaneous - The **Details** tab in content view now provides information about the Section the content item belongs to. *[Image: Section details in Details tab]* - You can now edit a content item directly from its parent's Sub-items table, and sort the table: *[Image: Sub-items table with Edit button and sorting]* - You can now restore from Trash content whose original Location has been deleted. - Pasted thead/tfood tags are now kept in RichText field type, and its Online Editor - Solr 6 is now supported in [Solr Bundle](https://doc.ibexa.co/en/latest/guide/search/solr) ### eZ Platform Enterprise Edition - Studio - It's now possible to configure landing page blocks used by the landing page editor in a simpler way. The configuration is done in a YAML file - *..lots of other bug fixes and smaller improvements..* ### eZ Platform Enterprise Edition - Studio Demo #### Tag and taxonomy management The eZ Enterprise Demo now uses the [Netgen Tags bundle](https://github.com/netgen/TagsBundle). This bundle was recently ported to eZ Platform and provides a powerful, solid and user-friendly way to categorize content using tags. The solution lets editors and administrators define their taxonomies in a dedicated interface. These taxonomies that are immediately available for editors working on content who want to categorize any content types. #### Miscellaneous - As an editor, I want to personalize content based on user persona - As an editor, I want to embed a video ## Full list of new features, improvements and bug fixes since v1.8.0 | eZ Platform | eZ Studio | | ------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | [List of changes for final of eZ Platform v1.9.0 on GitHub](https://github.com/ezsystems/ezplatform/releases/tag/v1.9.0) | [List of changes for final for eZ Platform Enterprise Edition v1.9.0 on GitHub](https://github.com/ezsystems/ezplatform-ee/releases/tag/v1.9.0) | | [List of changes for rc1 of eZ Platform v1.9.0 on GitHub](https://github.com/ezsystems/ezplatform/releases/tag/v1.9.0-rc1) | [List of changes for rc1 for eZ Platform Enterprise Edition v1.9.0 on GitHub](https://github.com/ezsystems/ezplatform-ee/releases/tag/v1.9.0-rc1) | | [List of changes for beta2 of eZ Platform v1.9.0 on GitHub](https://github.com/ezsystems/ezplatform/releases/tag/v1.9.0-beta2) | [List of changes for beta1 of eZ Platform Enterprise Edition v1.9.0 on GitHub](https://github.com/ezsystems/ezplatform-ee/releases/tag/v1.9.0-beta1) | ### Installation [Installation Guide](https://doc.ibexa.co/en/latest/getting_started/install_ez_platform) [Technical Requirements](https://doc.ibexa.co/en/latest/getting_started/requirements) ### Download #### eZ Platform - Download at [eZPlatform.com](http://ezplatform.com/#download) #### eZ Enterprise - [Customers: eZ Enterprise subscription (BUL License)](https://support.ez.no/Downloads) - Partners: Test & Trial software access (TTL License) If you would like to become familiar with the products, [request a demo](https://www.ibexa.co/forms/request-a-demo). ### Updating To update the product, follow the [updating guide](https://doc.ibexa.co/en/latest/updating/updating/). # eZ Platform v1.8.0 **The FAST TRACK v1.8.0 release of eZ Platform and eZ Platform Enterprise Edition is available as of February 16, 2017.** If you're looking for the Long Term Support (LTS) release, see ## Notable Changes Since v1.7.0 LTS ### eZ Platform #### User Interface - In Universal Discovery Widget (UDW) the browse view now uses a completely new browser widget, which replaces Treeview. This solves limitations on how many items you can browse for, and provides a more intuitive user experience. - Improvements in the Online Editor: - You now have the ability to rearrange elements in the editor by moving them up and down. - You can now add links to internal content items in the Online Editor, decide in which tab the link should open, and set link title: - Improvements to the Sub-Items view of a content item: You can now sort content items by clicking column headings - The main titles of the ContentTypeView now expand and retract with an accordion function - Updated and added icons for the Admin Interface - The whole interface of PlatformUI is now translatable using Crowdin, including in-context translation where you can navigate the interface while translating. A glossary has been established to aid in unified usage of terminology throughout. [Contributions welcome](https://crowdin.com/project/ezplatform)! #### Under the Hood - New opt-in approach to HttpCache to improve usability and performance by means of: - Cache multi-tagging: allowing you to tag pages with, for example, path, location, type, or parent, so the repository can clear cache in a more targeted, accurate, and flexible way, getting rid of any "clear all" situations on complex operations. - For Varnish this uses [xkey](https://github.com/varnish/varnish-modules/blob/master/docs/vmod_xkey.rst) instead of BAN, enabling greater performance by allowing you to control grace time. - This also places HttpCache in a separate repo, allowing it to grow independently: see  - New `content/publish` policy to be able to configure `content/edit` rights independently from publish rights - Community-provided translations of the user interface may be imported individually to conserve resources - Replaced deprecated templating helper assets with assets packages service - Localization of handlebar templates - Also part of v1.7.1 from the end of January: - Solr: Solving last issues in UI hindering relative ranking of search results from working properly - API: Respect `defaultAlwaysAvailable` setting on `newContentCreateStruct` solving issues with for instance Kaliop Migrations bundle use - Landing pages: Better support for wider range of multi-site setups - Online Editor: Ability to change a paragraph to header and back *For the complete list of fixes and improvements, see the GitHub release notes: * ### eZ Platform Enterprise Edition #### Studio - New fields are available for the Form Builder: - URL - Date - Checkbox - Radio - Dropdown - Captcha - File Upload #### Under the Hood - StudioUI is now fully ready for Internationalization ### Updated eZ Platform/EE Demo Distributions - You can now search and filter products in the Product Page of the EE Demo distribution: ## Full list of new features, improvements and bug fixes since v1.7.0 LTS: | eZ Platform | eZ Studio | | ------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | [List of changes for final of eZ Platform v1.8.0 on GitHub](https://github.com/ezsystems/ezplatform/releases/tag/v1.8.0) | [List of changes for final for eZ Platform Enterprise Edition v1.8.0 on GitHub](https://github.com/ezsystems/ezplatform-ee/releases/tag/v1.8.0) | | [List of changes for rc1 of eZ Platform v1.8.0 on GitHub](https://github.com/ezsystems/ezplatform/releases/tag/v1.8.0-rc1) | [List of changes for rc1 for eZ Platform Enterprise Edition v1.8.0 on GitHub](https://github.com/ezsystems/ezplatform-ee/releases/tag/v1.8.0-rc1) | | [List of changes for beta1 of eZ Platform v1.8.0 on GitHub](https://github.com/ezsystems/ezplatform/releases/tag/v1.8.0-beta1) | [List of changes for beta2 of eZ Platform Enterprise Edition v1.8.0 on GitHub](https://github.com/ezsystems/ezplatform-ee/releases/tag/v1.8.0-beta2) | ### Installation [Installation Guide](https://doc.ibexa.co/en/latest/getting_started/install_ez_platform) [Technical Requirements](https://doc.ibexa.co/en/latest/getting_started/requirements) ### Download #### eZ Platform - Download at [eZPlatform.com](http://ezplatform.com/#download) #### eZ Enterprise - [Customers: eZ Enterprise subscription (BUL License)](https://support.ez.no/Downloads) - Partners: Test & Trial software access (TTL License) If you would like to become familiar with the products, [request a demo](https://www.ibexa.co/forms/request-a-demo). ### Updating To update the product, follow the [updating guide](https://doc.ibexa.co/en/latest/updating/updating/). # eZ Platform v1.7.0 LTS **The v1.7.0 release of eZ Platform and eZ Platform Enterprise Edition is available as of December 15, 2016.** **** LTS Info eZ Platform Enterprise Edition v1.7.0 is the first version of the 2017 Long Term Support ("LTS") release which is maintained and supported until December 2019. As of v1.7.0, PHP requirements have been updated to remove PHP 5.5, leaving PHP 5.6 and 7.0 as supported PHP versions. With the LTS release, the [new product naming](http://ez.no/Blog/eZ-Announces-Name-Changes-to-Product-Portfolio) takes effect: "eZ Platform" for the Open Source edition, and "eZ Platform Enterprise Edition" for subscribers. ## Notable Changes Since v1.6.0 ### eZ Platform (Open Source) - **i18n! Internationalization of the eZ Platform** User Interface is now possible. The new system selects the language to use based on the browser settings of the end user. The system makes it possible to create translations for eZ Platform UI. Studio internationalization and translations ready to use are shipped in further releases. Community members are more than welcome to contribute to the translation process. - **Universal Discovery Widget** ("UDW") provides a range of small improvements. The most noticeable one is the preview of content which is more usable and also provides a way to get a full preview of the content object. - The **Admin** panel now provides a way to get a clean **digest view of content types** configured in the system, with the ability to clearly get access to properties and field definitions. - The online editor also brings a range of improvements that improve the editorial experience. The most noticeable one is to offer the possibility to switch from Headings to Paragraph styles for the same element. ##### Notable technical improvements: - Search: - Solr Search Engine: Plugins, extend the Solr index with custom data on Content, Translation and Location block level - For when you need to extend the index with additional data not applicable for FieldType custom fields feature - *[See Solr Bundle documentation for more info](https://doc.ibexa.co/en/2.5/guide/search/solr/)* - Solr Search Engine: Support for FieldRelation on location search - Legacy Search Engine: Improve word boundaries detection - ezplatform:reindex added, a generic command for reindexing search index on the SiteAccess configured search engine - Extensibility: - QueryType's now support using alias when being used as service so you can define several services with same  QueryType class - Example: Generic location child QueryType being reused several times for specific services for article or blog post listings - API: - New method:`Location->getSortClauses()` to get Sort Clauses based on what kind of sorting has been set on the Location - Add Content Version archives limit by configuration & enforce on publish - Debug: - ez-support-tools:dump-info command now able to dump system info in several formats, and default is now json - *Making it more useful for attaching system info when reporting issues* - Add SiteAccess collector to debug toolbar - Make IO exceptions more user friendly - Make it possible to retrieve original exception when repo->commit() fails *For more fixes and improvements scroll down for full change log.* ### eZ Platform Enterprise Edition (with Studio) - You can now use eZ Personalization service to create highly personalized landing pages. The Studio **Personalization Block** available out of the box lets the editor create a block that renders a list of content items personalized to each and every visitor. The interface lets the editor decide which of the Personalization scenarios configured in the eZ Personalization back end, and also the template for rendering, should be used. - You can now take advantage of the **Date-Based Publishing** feature – when editing a draft, instead of publishing the content immediately you can select the date and time at which it's automatically published. All your content scheduled to be published are accessible in a dedicated widget on the dashboard. - Create forms in your landing page with the **Form Builder**. A special Form Block allows you to add forms with different types of fields to the landing page. This system has been designed to be extended, so that you can create your own form fields. The system also provide an interface to access the data that has been collected, and download it as CSV files. Submitted results can be previewed in the UI or downloaded in a CSV file, and a designated person is notified of submissions by email. ### Updated Demo Sites The Enterprise demo site has been significantly improved featuring a new **Product content type** that is used to show products in the Tasteful Planet demo. The product we used are meals that, in a non-demo ideal world, would be available to order and consume. This ordering part isn't in the demo, nevertheless, the content looks really yummy... Other improvements includes the good setup of all content type field categories and the demonstration of basic SEO field types. Demo content itself has also been upgraded with more content to better demonstrate the capabilities. ### Installation [Installation Guide](https://doc.ibexa.co/en/latest/getting_started/install_ez_platform) [Technical Requirements](https://doc.ibexa.co/en/latest/getting_started/requirements) ### Download #### eZ Platform - Download at [eZPlatform.com](http://ezplatform.com/#download) #### eZ Enterprise - [Customers: eZ Enterprise subscription (BUL License)](https://support.ez.no/Downloads) - Partners: Test & Trial software access (TTL License) If you would like to become familiar with the products, [request a demo](https://www.ibexa.co/forms/request-a-demo). ### Updating To update the product, follow the [updating guide](https://doc.ibexa.co/en/latest/updating/updating/).