diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 08e37fdeb4..d7940c9922 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -89,9 +89,10 @@ jobs: TOKEN: ${{ secrets.EZROBOT_PAT }} run: | curl -H "Authorization: token $TOKEN" -L https://github.com/ibexa/vale-styles/archive/refs/heads/main.zip -o vale.zip - unzip vale.zip + unzip vale.zip -d vale rm vale.zip - mv vale-styles-main/* vale-styles-main/.vale.ini . + rm -rf vale/vale-styles-main/tests + mv vale/vale-styles-main/* vale/vale-styles-main/.vale.ini . - name: Run Vale.sh uses: vale-cli/vale-action@v2 diff --git a/.github/workflows/code_samples.yaml b/.github/workflows/code_samples.yaml index 9764f2ab1a..6609b8dde7 100644 --- a/.github/workflows/code_samples.yaml +++ b/.github/workflows/code_samples.yaml @@ -57,6 +57,10 @@ jobs: continue-on-error: true run: composer check-rector + - name: Run YAML snippet tests + continue-on-error: true + run: composer check-yaml + code-samples-inclusion-check: name: Check code samples inclusion runs-on: ubuntu-latest @@ -83,7 +87,7 @@ jobs: - name: Log target branch code_samples usage if: steps.list.outputs.CODE_SAMPLES_CHANGE != '' run: | - git fetch origin + git fetch origin --depth=1 ${{ github.head_ref }} git checkout origin/${{ github.head_ref }} -- tools/code_samples/code_samples_usage.php php tools/code_samples/code_samples_usage.php ${{ steps.list.outputs.CODE_SAMPLES_CHANGE }} > $HOME/code_samples_usage_target.txt diff --git a/.gitignore b/.gitignore index d243a010bd..10f1a32e27 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ auth.json yarn.lock docs/css/*.map .deptrac.cache +.phpunit.result.cache diff --git a/code_samples/back_office/components/twig_components.yaml b/code_samples/back_office/components/twig_components.yaml index 6e0458f4d0..20d8c3caa2 100644 --- a/code_samples/back_office/components/twig_components.yaml +++ b/code_samples/back_office/components/twig_components.yaml @@ -12,7 +12,6 @@ ibexa_twig_components: priority: 0 arguments: content: 'Hello world!' - admin-ui-user-menu: duplicated_user_menu: type: menu arguments: diff --git a/code_samples/back_office/online_editor/config/packages/custom_plugin.yaml b/code_samples/back_office/online_editor/config/packages/custom_plugin.yaml deleted file mode 100644 index 426b9b75b9..0000000000 --- a/code_samples/back_office/online_editor/config/packages/custom_plugin.yaml +++ /dev/null @@ -1,13 +0,0 @@ -ibexa: - system: - admin_group: - fieldtypes: - ibexa_richtext: - toolbars: - paragraph: - buttons: - date: - priority: 0 -ibexa_fieldtype_richtext: - alloy_editor: - extra_plugins: [date] diff --git a/code_samples/front/shop/order-management/config/packages/ibexa.yaml b/code_samples/front/shop/order-management/config/packages/ibexa.yaml index e17cfbc9e5..310eaca7c3 100644 --- a/code_samples/front/shop/order-management/config/packages/ibexa.yaml +++ b/code_samples/front/shop/order-management/config/packages/ibexa.yaml @@ -65,7 +65,7 @@ framework: to: - dropped -// ... +# ... ibexa: repositories: diff --git a/code_samples/front/shop/payment/src/bundle/Resources/config/services/payment_method.yaml b/code_samples/front/shop/payment/src/bundle/Resources/config/services/payment_method.yaml index b179640c05..ef3b33c817 100644 --- a/code_samples/front/shop/payment/src/bundle/Resources/config/services/payment_method.yaml +++ b/code_samples/front/shop/payment/src/bundle/Resources/config/services/payment_method.yaml @@ -8,7 +8,7 @@ services: $domain: tags: - { name: ibexa.payment.payment_method.type, alias: new_payment_method_type } -services: + App\Payment\PaymentMethod\Voter\NewPaymentMethodTypeVoter: tags: - - { name: ibexa.payment.payment_method.voter, type: new_payment_method_type } \ No newline at end of file + - { name: ibexa.payment.payment_method.voter, type: new_payment_method_type } diff --git a/code_samples/recommendations/config/packages/ibexa_connector_raptor.yaml b/code_samples/recommendations/config/packages/ibexa_connector_raptor.yaml index 61c90252b1..1898c35aae 100644 --- a/code_samples/recommendations/config/packages/ibexa_connector_raptor.yaml +++ b/code_samples/recommendations/config/packages/ibexa_connector_raptor.yaml @@ -3,14 +3,14 @@ ibexa: : connector_raptor: enabled: true - customer_id: ~ # Required + customer_id: "12345" # Required tracking_type: client # One of: "client" or "server" # Raptor Recommendations API key - recommendations_api_key: ~ # Required + recommendations_api_key: "your_api_key_here" # Required - # Raptor Recommendations API URL, optional, set by default - recommendations_api_url: '%ibexa.connector.raptor.recommendations.api_url%' + # Raptor Recommendations API URI, optional, set by default + recommendations_api_uri: '%ibexa.connector.raptor.recommendations.api_uri%' ibexa_connector_raptor: # When enabled, tracking exceptions are thrown instead of being silently handled strict_exceptions: true diff --git a/composer.json b/composer.json index e9415fc8fd..a2c6f57fb6 100644 --- a/composer.json +++ b/composer.json @@ -4,6 +4,9 @@ "type": "library", "license": "GNU General Public License v2.0", "autoload-dev": { + "psr-4": { + "Ibexa\\Tests\\Documentation\\": "tests/" + } }, "repositories": [ { @@ -15,6 +18,9 @@ "php": "^8.3" }, "require-dev": { + "phpunit/phpunit": "^11.0", + "symfony/yaml": "^7.0", + "ibexa/connector-gemini": "5.0.x-dev", "ibexa/automated-translation": "5.0.x-dev", "ibexa/code-style": "~2.0.0", "friendsofphp/php-cs-fixer": "^3.30", @@ -51,7 +57,7 @@ "ibexa/page-builder": "5.0.x-dev", "ibexa/order-management": "5.0.x-dev", "ibexa/calendar": "5.0.x-dev", - "ibexa/payment": "5.0.x-dev", + "ibexa/payment": "~5.0.x-dev", "ibexa/shipping": "5.0.x-dev", "ibexa/fieldtype-matrix": "5.0.x-dev", "ibexa/storefront": "5.0.x-dev", @@ -85,21 +91,33 @@ "ibexa/cdp": "~5.0.x-dev", "ibexa/connector-raptor": "~5.0.x-dev", "ibexa/image-editor": "~5.0.x-dev", - "ibexa/integrated-help": "~5.0.x-dev" + "ibexa/integrated-help": "~5.0.x-dev", + "ibexa/site-context": "~5.0.x-dev", + "ibexa/fieldtype-richtext-rte": "~5.0.x-dev", + "ibexa/site-factory": "~5.0.x-dev", + "ibexa/ckeditor-premium": "~5.0.x-dev", + "ibexa/measurement": "~5.0.x-dev", + "ibexa/connector-actito": "~5.0.x-dev", + "ibexa/fastly": "~5.0.x-dev" }, "scripts": { "fix-cs": "php-cs-fixer fix --config=.php-cs-fixer.php -v --show-progress=dots", "check-cs": "@fix-cs --dry-run", "phpstan": "phpstan analyse", "deptrac": "deptrac analyse", - "check-rector": "rector process --dry-run --ansi" + "check-rector": "rector process --dry-run --ansi", + "check-yaml": "phpunit --group yaml", + "phpunit": "phpunit --exclude-group=yaml", + "yaml-update-baseline": "php tests/generate-yaml-baseline.php" }, "scripts-descriptions": { "fix-cs": "Automatically fixes code style in all files", "check-cs": "Run code style checker for all files", "phpstan": "Run static code analysis", "deptrac": "Run Deptrac architecture testing", - "check-rector": "Check for code refactoring opportunities" + "check-rector": "Check for code refactoring opportunities", + "check-yaml": "Run PHPUnit tests (YAML validation)", + "yaml-update-baseline": "Regenerate tests/yaml-validation-baseline.yaml from current failures" }, "config": { "allow-plugins": false diff --git a/docs/administration/back_office/customize_search_suggestion.md b/docs/administration/back_office/customize_search_suggestion.md index 158b3384c2..2764115e25 100644 --- a/docs/administration/back_office/customize_search_suggestion.md +++ b/docs/administration/back_office/customize_search_suggestion.md @@ -17,8 +17,9 @@ ibexa: system: : search: - min_query_length: 3 - result_limit: 5 + suggestion: + min_query_length: 3 + result_limit: 5 ``` ## Add custom suggestion source diff --git a/docs/administration/configuration/dynamic_configuration.md b/docs/administration/configuration/dynamic_configuration.md index 09f9cfbf42..c847e0899d 100644 --- a/docs/administration/configuration/dynamic_configuration.md +++ b/docs/administration/configuration/dynamic_configuration.md @@ -18,7 +18,7 @@ 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 diff --git a/docs/api/graphql/graphql_customization.md b/docs/api/graphql/graphql_customization.md index 5ebf97b4f0..be6421d049 100644 --- a/docs/api/graphql/graphql_customization.md +++ b/docs/api/graphql/graphql_customization.md @@ -68,9 +68,9 @@ Mutation: createSomething: builder: Mutation builderConfig: - inputType: CreateSomethingInput - payloadType: SomethingPayload - mutateAndGetPayload: '@=mutation('CreateSomething', [value])' + inputType: CreateSomethingInput + payloadType: SomethingPayload + mutateAndGetPayload: "@=mutation('CreateSomething', [value])" CreateSomethingInput: type: relay-mutation-input diff --git a/docs/api/rest_api/rest_api_authentication.md b/docs/api/rest_api/rest_api_authentication.md index f7f77366f8..82b504a010 100644 --- a/docs/api/rest_api/rest_api_authentication.md +++ b/docs/api/rest_api/rest_api_authentication.md @@ -325,10 +325,15 @@ For more information, see [HTTP Authentication: Basic and Digest Access Authenti 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: ```yaml +security: + firewalls: + # ... ibexa_rest: host: ^api\.example\.com$ http_basic: realm: Ibexa DXP REST API + #ibexa_front: + # ... ``` !!! caution "Back office uses REST API" diff --git a/docs/cdp/cdp_installation.md b/docs/cdp/cdp_installation.md index 73ba9dd5ae..f84719a45e 100644 --- a/docs/cdp/cdp_installation.md +++ b/docs/cdp/cdp_installation.md @@ -27,11 +27,14 @@ Symfony Flex installs and activates the package. After an installation process is finished, go to `config/packages/security.yaml` and uncomment `ibexa_cdp` rule. ```yaml -ibexa_cdp: - pattern: /cdp/webhook - guard: - authenticator: 'Ibexa\Cdp\Security\CdpRequestAuthenticator' - stateless: true +security: + firewalls: + # ... + ibexa_cdp: + request_matcher: Ibexa\Cdp\Security\RequestMatcher + custom_authenticators: + - 'Ibexa\Cdp\Security\CdpRequestAuthenticator' + stateless: true ``` Now, you can configure [[= product_name_cdp =]]. diff --git a/docs/commerce/checkout/reorder.md b/docs/commerce/checkout/reorder.md index 4d022782e4..96f83c6bed 100644 --- a/docs/commerce/checkout/reorder.md +++ b/docs/commerce/checkout/reorder.md @@ -54,11 +54,11 @@ framework: 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 ``` diff --git a/docs/commerce/payment/enable_paypal_payments.md b/docs/commerce/payment/enable_paypal_payments.md index 3bccfdcdc5..2e4b890c25 100644 --- a/docs/commerce/payment/enable_paypal_payments.md +++ b/docs/commerce/payment/enable_paypal_payments.md @@ -42,5 +42,4 @@ ibexa: type: pp_express_checkout: name: "Translated PayPal Express Checkout name" - ``` diff --git a/docs/commerce/payment/enable_stripe_payments.md b/docs/commerce/payment/enable_stripe_payments.md index 105ed62402..5d03bd8829 100644 --- a/docs/commerce/payment/enable_stripe_payments.md +++ b/docs/commerce/payment/enable_stripe_payments.md @@ -43,5 +43,4 @@ ibexa: type: strp_checkout: name: "Translated Stripe Checkout name" - ``` diff --git a/docs/commerce/payment/payum_integration.md b/docs/commerce/payment/payum_integration.md index 7da7da20b1..408515f257 100644 --- a/docs/commerce/payment/payum_integration.md +++ b/docs/commerce/payment/payum_integration.md @@ -43,7 +43,7 @@ ibexa_connector_payum: refunded: cancelled captured: pending authorized: authorized -[...] +# ... ``` ## Payment service name translations diff --git a/docs/commerce/storefront/configure_storefront.md b/docs/commerce/storefront/configure_storefront.md index bdc96c02c8..5d69a73251 100644 --- a/docs/commerce/storefront/configure_storefront.md +++ b/docs/commerce/storefront/configure_storefront.md @@ -94,9 +94,10 @@ Settings for a Storefront user are configured under the `ibexa.system..st ibexa: system: site_group: - user_settings_groups: - - location - - custom_group + storefront: + user_settings_groups: + - location + - custom_group ``` By default, only the `location` user settings is provided: diff --git a/docs/content_management/collaborative_editing/configure_collaborative_editing.md b/docs/content_management/collaborative_editing/configure_collaborative_editing.md index 482040f3e1..268f774ff4 100644 --- a/docs/content_management/collaborative_editing/configure_collaborative_editing.md +++ b/docs/content_management/collaborative_editing/configure_collaborative_editing.md @@ -57,14 +57,15 @@ security: ```yaml 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 + firewalls: + 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 diff --git a/docs/content_management/data_migration/managing_migrations.md b/docs/content_management/data_migration/managing_migrations.md index 072a7a0e6d..c96e078208 100644 --- a/docs/content_management/data_migration/managing_migrations.md +++ b/docs/content_management/data_migration/managing_migrations.md @@ -50,7 +50,7 @@ You can configure a different folder by using the following settings: ``` yaml ibexa_migrations: - migration_directory: %kernel.project_dir%/src/Migrations/MyMigrations/ + migration_directory: '%kernel.project_dir%/src/Migrations/MyMigrations/' migrations_files_subdir: migration_files ``` @@ -64,7 +64,6 @@ ibexa_migrations: ``` yaml ibexa_migrations: migration_directory: '%kernel.project_dir%/data/' - ... ``` Then, when you run the migration command, you must use the [`--siteaccess` option](exporting_data.md#siteaccess) and provide the name of the SiteAccess that you want to migrate. diff --git a/docs/content_management/field_types/field_type_storage.md b/docs/content_management/field_types/field_type_storage.md index 34999a91ce..12af0bc85b 100644 --- a/docs/content_management/field_types/field_type_storage.md +++ b/docs/content_management/field_types/field_type_storage.md @@ -147,7 +147,7 @@ services: autoconfigure: true public: false - App\FieldType\MyField\Storage\MyFieldStorage: ~ + App\FieldType\MyField\Storage\MyFieldStorage: tags: - {name: ibexa.field_type.storage.external.handler, alias: myfield} ``` diff --git a/docs/content_management/field_types/form_and_template.md b/docs/content_management/field_types/form_and_template.md index fed81cc459..b5f0eba859 100644 --- a/docs/content_management/field_types/form_and_template.md +++ b/docs/content_management/field_types/form_and_template.md @@ -190,7 +190,7 @@ If you don't use the design engine, apply the following configuration: ``` yaml ibexa: - systems: + system: admin_group: field_templates: - { template: 'adminui/field/custom_field_view.html.twig', priority: 10 } diff --git a/docs/content_management/images/add_image_asset_from_dam.md b/docs/content_management/images/add_image_asset_from_dam.md index 8c7c236fd3..52b91bbc7c 100644 --- a/docs/content_management/images/add_image_asset_from_dam.md +++ b/docs/content_management/images/add_image_asset_from_dam.md @@ -49,15 +49,15 @@ Next, in `config/packages/ibexa.yaml`, set the `dam.html.twig` template for the For more information about displaying content, see [Content rendering](render_content.md). ``` yaml - ibexa: - system: - site: - content_view: - embed: - image_dam: - template: '@ibexadesign/embed/dam.html.twig' - match: - Identifier\ContentType: +ibexa: + system: + site: + content_view: + embed: + image_dam: + template: '@ibexadesign/embed/dam.html.twig' + match: + Identifier\ContentType: ``` In your [configuration file](configuration.md#configuration-files) add the following configuration: diff --git a/docs/content_management/taxonomy/taxonomy.md b/docs/content_management/taxonomy/taxonomy.md index f01ddb75df..118b787a18 100644 --- a/docs/content_management/taxonomy/taxonomy.md +++ b/docs/content_management/taxonomy/taxonomy.md @@ -191,10 +191,9 @@ By default, the system returns three suggestions. You can change the default number if needed by altering the following setting: ``` yaml hl_lines="4" -ibexa: - taxonomy: +ibexa_taxonomy: text_to_taxonomy: - default_suggested_taxonomies_limit: 5 + default_suggested_taxonomies_limit: 5 ``` You can also override this setting per AI action by editing its configuration. diff --git a/docs/content_management/url_management/url_management.md b/docs/content_management/url_management/url_management.md index 20af85356b..ed56dadc75 100644 --- a/docs/content_management/url_management/url_management.md +++ b/docs/content_management/url_management/url_management.md @@ -67,13 +67,13 @@ ibexa: url_checker: handlers: http: - enabled: true - batch_size: 64 + enabled: true + batch_size: 64 https: - enabled: true - ignore_certificate: false + enabled: true + ignore_certificate: false mailto: - enabled: false + enabled: false ``` Available options are protocol-specific. @@ -128,7 +128,6 @@ Then you must register the service with an `ibexa.url_checker.handler` tag, like ```yaml app.url_checker.handler.custom: class: 'App\URLChecker\Handler\CustomHandler' - ... tags: - { name: ibexa.url_checker.handler, scheme: custom } ``` diff --git a/docs/content_management/user_generated_content.md b/docs/content_management/user_generated_content.md index 4a12ecc3bf..a35612334e 100644 --- a/docs/content_management/user_generated_content.md +++ b/docs/content_management/user_generated_content.md @@ -56,14 +56,17 @@ For example, `/content/edit/draft/1/5/eng-GB` enables you to edit draft 5 of con You can use custom templates for the content editing forms. -Define the templates under the `ibexa.system..content_edit.templates` [configuration key](configuration.md#configuration-files): +Define the templates under the `ibexa.system..content_edit_view` [configuration key](configuration.md#configuration-files): ``` yaml ibexa: system: default: - content_edit: - templates: - edit: content/edit/content_edit.html.twig - create_draft: content/edit/content_create_draft.html.twig + content_edit_view: + full: + : + template: content/edit/content_edit.html.twig + match: true + params: + viewbaseLayout: '@ibexadesign/ui/layout.html.twig' ``` diff --git a/docs/customer_management/cp_page_builder.md b/docs/customer_management/cp_page_builder.md index 69e3bdd6b6..f81510d345 100644 --- a/docs/customer_management/cp_page_builder.md +++ b/docs/customer_management/cp_page_builder.md @@ -51,7 +51,7 @@ ibexa: languages: [ eng-GB ] content: tree_root: - location_id: location_id_of_customer_portal + location_id: 12345 # location_id_of_customer_portal excluded_uri_prefixes: [ /media/, /images/ ] ``` @@ -135,7 +135,7 @@ ibexa: languages: [ eng-GB ] content: tree_root: - location_id: location_id_of_customer_portals_root_folder + location_id: 12345 # location_id_of_customer_portals_root_folder excluded_uri_prefixes: [ /media/, /images/ ] ``` @@ -233,7 +233,7 @@ ibexa: page_layout: "@App/my_page_layout.html.twig" content: tree_root: - location_id: location_id_of_customer_portals_root_folder + location_id: 12345 #location_id_of_customer_portals_root_folder excluded_uri_prefixes: [ /media/, /images/ ] ``` diff --git a/docs/infrastructure_and_maintenance/cache/http_cache/content_aware_cache.md b/docs/infrastructure_and_maintenance/cache/http_cache/content_aware_cache.md index ab84406005..6e93ca73d3 100644 --- a/docs/infrastructure_and_maintenance/cache/http_cache/content_aware_cache.md +++ b/docs/infrastructure_and_maintenance/cache/http_cache/content_aware_cache.md @@ -295,7 +295,7 @@ With the same content structure as above, the `[Child]` location is moved below The new structure is then: -```yaml +```text - [Home] (content-id=52, location-id=2) ez-all c52 ct42 l2 pl1 p1 p2 | diff --git a/docs/infrastructure_and_maintenance/cache/http_cache/reverse_proxy.md b/docs/infrastructure_and_maintenance/cache/http_cache/reverse_proxy.md index 8a743eb240..3cb48b9002 100644 --- a/docs/infrastructure_and_maintenance/cache/http_cache/reverse_proxy.md +++ b/docs/infrastructure_and_maintenance/cache/http_cache/reverse_proxy.md @@ -142,6 +142,9 @@ ibexa: If the Varnish server is protected by Basic Auth, specify the Basic Auth credentials within the `purge_servers` setting using the format: ``` yaml +ibexa: + system: + my_siteaccess_group: http_cache: purge_servers: [http://myuser:mypasswd@my.varnish.server:8081] ``` diff --git a/docs/multisite/site_factory/site_factory.md b/docs/multisite/site_factory/site_factory.md index 74bed2fefb..3afd0628e9 100644 --- a/docs/multisite/site_factory/site_factory.md +++ b/docs/multisite/site_factory/site_factory.md @@ -204,7 +204,7 @@ Keep in mind that with disabled Site Factory you're unable to add new sites or u doctrine: dbal: connections: - ... + # ... # This connection is dedicated for SiteFactory to avoid known issues site_factory: ``` @@ -214,7 +214,7 @@ doctrine: ``` yaml framework: cache: - ... + # ... pools: # This pool should be used only by SiteFactory bundle site_factory_pool: diff --git a/docs/recommendations/raptor_integration/connector_installation_configuration.md b/docs/recommendations/raptor_integration/connector_installation_configuration.md index dfc2645400..3e133a440f 100644 --- a/docs/recommendations/raptor_integration/connector_installation_configuration.md +++ b/docs/recommendations/raptor_integration/connector_installation_configuration.md @@ -35,7 +35,7 @@ To configure the Raptor connector, use the `ibexa.system..connector_rapto - `client` - tracking is executed in the browser using JavaScript snippets generated by the [Twig functions](recommendations_twig_functions.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"](connector_installation_configuration.md#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. +- `recommendations_api_uri` (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). diff --git a/docs/release_notes/ez_platform_v2.4.md b/docs/release_notes/ez_platform_v2.4.md index 90562b8ada..16d92cbdb2 100644 --- a/docs/release_notes/ez_platform_v2.4.md +++ b/docs/release_notes/ez_platform_v2.4.md @@ -197,18 +197,18 @@ The biggest benefit of this feature is saving load time on complex landing pages 2\. Add the following configuration to `/app/config/config.yml` ``` yaml - lexik_jwt_authentication: - secret_key: '%secret%' - encoder: + 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 + 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. @@ -218,23 +218,23 @@ The biggest benefit of this feature is saving load time on complex landing pages 3\. Add `EzSystems\EzPlatformPageBuilder\Security\EditorialMode\TokenAuthenticator` authentication provider to `ezpublish_front` firewall before `form_login` in `app/config/security.yml`: ``` yaml - security: + security: # ... - firewalls: - ezpublish_front: - # ... - simple_preauth: - authenticator: 'EzSystems\EzPlatformPageBuilder\Security\EditorialMode\TokenAuthenticator' - form_login: - require_previous_session: false - # ... + 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`: ``` yaml - # ... - parameters: + # ... + parameters: # ... page_builder.token_authenticator.enabled: true ``` diff --git a/docs/search/search_engines/elasticsearch/configure_elasticsearch.md b/docs/search/search_engines/elasticsearch/configure_elasticsearch.md index 3e05c45665..38c9cf33bb 100644 --- a/docs/search/search_engines/elasticsearch/configure_elasticsearch.md +++ b/docs/search/search_engines/elasticsearch/configure_elasticsearch.md @@ -182,7 +182,7 @@ If your Elasticsearch server is protected by HTTP authentication, you must provi In the basic authentication, you must pass the following parameters: ``` yaml - +: # ... authentication: type: basic @@ -377,14 +377,14 @@ Index names use the following pattern: 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: - ``` yaml - ibexa_elasticsearch: +``` yaml +ibexa_elasticsearch: # ... index_templates: default_en_us: patterns: ['default_*', '*eng_us*'] - # ... - ``` + # ... +``` - `settings` - Settings under this key control all aspects related to an index. @@ -392,21 +392,21 @@ For more information and a list of available settings, see [Elasticsearch docume 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: - ``` yaml - ibexa_elasticsearch: - # ... - index_templates: - default: - # ... - settings: - analysis: - normalizer: - lowercase_normalizer: - type: custom - char_filter: [] - filter: lowercase - # ... - ``` +``` yaml +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. diff --git a/docs/search/search_engines/solr_search_engine/install_solr.md b/docs/search/search_engines/solr_search_engine/install_solr.md index edf2a76c0c..b7de523435 100644 --- a/docs/search/search_engines/solr_search_engine/install_solr.md +++ b/docs/search/search_engines/solr_search_engine/install_solr.md @@ -160,9 +160,9 @@ 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: ``` yaml - env(SEARCH_ENGINE): solr - env(SOLR_DSN): 'http://localhost:8983/solr' - env(SOLR_CORE): collection1 +env(SEARCH_ENGINE): solr +env(SOLR_DSN): 'http://localhost:8983/solr' +env(SOLR_CORE): collection1 ``` ### Configure Solr version diff --git a/docs/templating/image_variations.md b/docs/templating/image_variations.md index cdcb8d9046..e4718dadfb 100644 --- a/docs/templating/image_variations.md +++ b/docs/templating/image_variations.md @@ -26,7 +26,9 @@ ibexa: : reference: null filters: - : + filter_name: + - parameter1 + - parameter2 ``` Variation name must be unique. diff --git a/docs/templating/templates/view_matcher_reference.md b/docs/templating/templates/view_matcher_reference.md index 687569c488..4e706fbeb1 100644 --- a/docs/templating/templates/view_matcher_reference.md +++ b/docs/templating/templates/view_matcher_reference.md @@ -235,7 +235,7 @@ match: ``` yaml match: - '@Ibexa\Taxonomy\View\Matcher\TaxonomyEntryBased\Id': [1, 2, 3]' + '@Ibexa\Taxonomy\View\Matcher\TaxonomyEntryBased\Id': [1, 2, 3] ``` ## Taxonomy entry identifier @@ -264,4 +264,4 @@ match: ``` yaml match: '@Ibexa\Taxonomy\View\Matcher\TaxonomyEntryBased\Taxonomy': 'product_category' -``` \ No newline at end of file +``` diff --git a/docs/users/invitations.md b/docs/users/invitations.md index 446e852902..ddcebdd860 100644 --- a/docs/users/invitations.md +++ b/docs/users/invitations.md @@ -30,13 +30,13 @@ If the SiteAccess isn't set, it falls back to the default `site` value. For example, use the following [configuration](configuration.md#configuration-files): ```yaml - ibexa: - system: - : - user_invitation: - hash_expiration_time: P7D - templates: - mail: "@@App/invitation/mail.html.twig" +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. diff --git a/docs/users/oauth_server.md b/docs/users/oauth_server.md index 45ac506920..bc12a9889f 100644 --- a/docs/users/oauth_server.md +++ b/docs/users/oauth_server.md @@ -89,7 +89,7 @@ In `config/packages/security.yaml`, uncomment the three following lines under th ```yaml security: #… - firewall: + firewalls: #… # Uncomment oauth2_token firewall if you wish to use product as an OAuth2 Server. diff --git a/docs/users/user_authentication.md b/docs/users/user_authentication.md index d2b7280301..9ec9e79c5d 100644 --- a/docs/users/user_authentication.md +++ b/docs/users/user_authentication.md @@ -66,7 +66,7 @@ services: App\EventListener\InteractiveLoginListener: arguments: ['@ibexa.api.service.user'] tags: - - { name: kernel.event_subscriber }  + - { name: kernel.event_subscriber } ``` Don't mix `MVCEvents::INTERACTIVE_LOGIN` event (specific to [[= product_name =]]) and `SecurityEvents::INTERACTIVE_LOGIN` event (fired by Symfony security component). diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000000..e084420e57 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,14 @@ + + + + + tests/ + + + diff --git a/tests/ConfigurationProvider.php b/tests/ConfigurationProvider.php new file mode 100644 index 0000000000..4dfbe49523 --- /dev/null +++ b/tests/ConfigurationProvider.php @@ -0,0 +1,178 @@ +container = $this->buildContainer(); + } + + public function hasExtension(string $alias): bool + { + return $this->container->hasExtension($alias); + } + + public function createConfiguration(string $alias): ConfigurationInterface + { + return $this->container->getExtension($alias)->getConfiguration([], $this->container); + } + + /** + * Recursively resolves %parameter% placeholders using the container's + * parameter bag, mirroring what the real Symfony kernel does before + * passing config to the Config component. Unknown parameters (custom app + * params not present in the test container) are left as-is. + * + * @param array $config + * + * @return array + */ + public function resolveParameters(array $config): array + { + /** @var array $result */ + $result = $this->resolveValue($this->container->getParameterBag(), $config); + + return $result; + } + + private function resolveValue(ParameterBagInterface $bag, mixed $value): mixed + { + if (is_array($value)) { + return array_map(fn (mixed $v): mixed => $this->resolveValue($bag, $v), $value); + } + + if (!is_string($value)) { + return $value; + } + + try { + return $bag->resolveValue($value); + } catch (ParameterNotFoundException) { + return $value; + } + } + + private function buildContainer(): ContainerBuilder + { + $container = new ContainerBuilder(); + $container->setParameter('kernel.debug', false); + $container->setParameter('kernel.bundles', []); + $container->setParameter('kernel.bundles_metadata', []); + $container->setParameter('kernel.project_dir', sys_get_temp_dir()); + $container->setParameter('kernel.environment', 'test'); + + $bundles = self::discoverBundles(); + + // Register all extensions before calling build() on any bundle, + // because some bundles call $container->getExtension('ibexa') during build(). + foreach ($bundles as $bundle) { + try { + $extension = $bundle->getContainerExtension(); + if ($extension !== null) { + $container->registerExtension($extension); + } + } catch (\Throwable) { + // Skip bundles whose extension cannot be instantiated. + } + } + + // build() registers parsers/factories into the extensions. + foreach ($bundles as $bundle) { + try { + $bundle->build($container); + } catch (\Throwable) { + // Skip bundles whose build() fails (e.g. missing sibling extensions). + } + } + + return $container; + } + + /** + * Returns all installed bundles with SecurityBundle and IbexaCoreBundle + * guaranteed first (other bundles may call getExtension('ibexa') or + * getExtension('security') during their build()). + * + * @return list + */ + private static function discoverBundles(): array + { + // These must be registered before any bundle that calls + // $container->getExtension('ibexa'/'security') inside build(). + $bundles = [ + new SecurityBundle(), + new IbexaCoreBundle(), + ]; + + $seen = [SecurityBundle::class, IbexaCoreBundle::class]; + + $vendorBase = __DIR__ . '/../vendor'; + $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($vendorBase)); + + foreach ($iterator as $file) { + if (!$file->isFile() || !preg_match('/\w+Bundle\.php$/', $file->getFilename())) { + continue; + } + + $content = file_get_contents($file->getPathname()); + preg_match('/^namespace (.+);/m', $content, $nsMatch); + preg_match('/^(?:(?:final|abstract)\s+)?class (\w+Bundle)\b/m', $content, $clsMatch); + + if (empty($nsMatch[1]) || empty($clsMatch[1])) { + continue; + } + + $fqcn = $nsMatch[1] . '\\' . $clsMatch[1]; + + if (!class_exists($fqcn) || in_array($fqcn, $seen, true)) { + continue; + } + + $reflection = new \ReflectionClass($fqcn); + if ($reflection->isAbstract() || !$reflection->implementsInterface(BundleInterface::class)) { + continue; + } + + $seen[] = $fqcn; + + try { + $bundles[] = new $fqcn(); + } catch (\Throwable) { + // Skip bundles that cannot be instantiated without arguments. + } + } + + return $bundles; + } +} diff --git a/tests/Markdown/MarkdownYamlExtractor.php b/tests/Markdown/MarkdownYamlExtractor.php new file mode 100644 index 0000000000..699c59853e --- /dev/null +++ b/tests/Markdown/MarkdownYamlExtractor.php @@ -0,0 +1,78 @@ + *)```\s*yaml[^\n]*\n(?P.*?)\n(?P=indent)```/ms'; + + private const string SKIP_PATTERN = '/include_(?:file|code)\s*\(|--8<--/'; + + /** + * @return iterable + */ + public function extract(string $content): iterable + { + if (!preg_match_all(self::FENCE_PATTERN, $content, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) { + return; + } + + foreach ($matches as $match) { + $body = $match['body'][0]; + $offset = $match['body'][1]; + + if (preg_match(self::SKIP_PATTERN, $body)) { + continue; + } + + $indent = $match['indent'][0]; + if ($indent !== '') { + $body = $this->stripIndentation($body, strlen($indent)); + } + + $line = substr_count(substr($content, 0, $offset), "\n") + 1; + + yield ['body' => $body, 'line' => $line]; + } + } + + private function stripIndentation(string $body, int $spaces): string + { + $prefix = str_repeat(' ', $spaces); + $lines = explode("\n", $body); + $stripped = array_map( + static fn (string $line): string => str_starts_with($line, $prefix) + ? substr($line, $spaces) + : $line, + $lines + ); + + return implode("\n", $stripped); + } +} diff --git a/tests/Markdown/MarkdownYamlExtractorTest.php b/tests/Markdown/MarkdownYamlExtractorTest.php new file mode 100644 index 0000000000..0575ae51df --- /dev/null +++ b/tests/Markdown/MarkdownYamlExtractorTest.php @@ -0,0 +1,224 @@ +extractor = new MarkdownYamlExtractor(); + } + + public function testExtractsNothing(): void + { + self::assertEmpty(iterator_to_array($this->extractor->extract('No code blocks here.'))); + self::assertEmpty(iterator_to_array($this->extractor->extract(''))); + } + + public function testIgnoresNonYamlFences(): void + { + $content = <<<'MD' + ```php + $x = 1; + ``` + + ```json + {"key": "value"} + ``` + MD; + + self::assertEmpty(iterator_to_array($this->extractor->extract($content))); + } + + public function testExtractsSingleBlock(): void + { + $content = <<<'MD' + Some text. + + ```yaml + foo: bar + ``` + + More text. + MD; + + $blocks = iterator_to_array($this->extractor->extract($content)); + + self::assertCount(1, $blocks); + self::assertSame('foo: bar', $blocks[0]['body']); + } + + public function testExtractsMultipleBlocks(): void + { + $content = <<<'MD' + ```yaml + first: 1 + ``` + + ```yaml + second: 2 + ``` + MD; + + $blocks = iterator_to_array($this->extractor->extract($content)); + + self::assertCount(2, $blocks); + self::assertSame('first: 1', $blocks[0]['body']); + self::assertSame('second: 2', $blocks[1]['body']); + } + + public function testReportsCorrectLineNumber(): void + { + $content = "line1\nline2\nline3\n```yaml\nfoo: bar\n```\n"; + + $blocks = iterator_to_array($this->extractor->extract($content)); + + self::assertCount(1, $blocks); + // The body starts on line 5 (after 4 preceding newlines inside the fence open) + self::assertSame(5, $blocks[0]['line']); + } + + public function testAcceptsSpaceBeforeLanguageTag(): void + { + $content = "``` yaml\nfoo: bar\n```\n"; + + $blocks = iterator_to_array($this->extractor->extract($content)); + + self::assertCount(1, $blocks); + self::assertSame('foo: bar', $blocks[0]['body']); + } + + public function testAcceptsTrailingAnnotations(): void + { + $content = "```yaml hl_lines=\"1 2\"\nfoo: bar\n```\n"; + + $blocks = iterator_to_array($this->extractor->extract($content)); + + self::assertCount(1, $blocks); + self::assertSame('foo: bar', $blocks[0]['body']); + } + + public function testStripsAdmonitionIndentation(): void + { + $content = <<<'MD' + !!! note + + ```yaml + foo: bar + baz: qux + ``` + MD; + + $blocks = iterator_to_array($this->extractor->extract($content)); + + self::assertCount(1, $blocks); + self::assertSame("foo: bar\nbaz: qux", $blocks[0]['body']); + } + + public function testSkipsBlocksWithIncludeFile(): void + { + $content = <<<'MD' + ```yaml + [[= include_file('some/file.yaml') =]] + ``` + MD; + + self::assertEmpty(iterator_to_array($this->extractor->extract($content))); + } + + public function testSkipsBlocksWithIncludeCode(): void + { + $content = <<<'MD' + ```yaml + [[= include_code('some/file.yaml', 1, 10) =]] + ``` + MD; + + self::assertEmpty(iterator_to_array($this->extractor->extract($content))); + } + + public function testSkipsBlocksWithSnippetMarker(): void + { + $content = <<<'MD' + ```yaml + --8<-- + some/file.yaml + --8<-- + ``` + MD; + + self::assertEmpty(iterator_to_array($this->extractor->extract($content))); + } + + public function testSkipsOnlyMatchingBlocksWhenMixed(): void + { + $content = <<<'MD' + ```yaml + [[= include_file('foo.yaml') =]] + ``` + + ```yaml + [[= include_code('bar.yaml', 1, 5) =]] + ``` + + ```yaml + real: config + ``` + MD; + + $blocks = iterator_to_array($this->extractor->extract($content)); + + self::assertCount(1, $blocks); + self::assertSame('real: config', $blocks[0]['body']); + } + + /** + * @param array $expected + */ + #[DataProvider('provideMultilineBlocks')] + public function testExtractsMultilineBody(string $content, array $expected): void + { + $blocks = iterator_to_array($this->extractor->extract($content)); + + self::assertCount(count($expected), $blocks); + foreach ($expected as $i => $exp) { + self::assertSame($exp['body'], $blocks[$i]['body'], "body at index $i"); + self::assertSame($exp['line'], $blocks[$i]['line'], "line at index $i"); + } + } + + /** + * @return iterable}> + */ + public static function provideMultilineBlocks(): iterable + { + yield 'nested mapping' => [ + "```yaml\nparent:\n child: value\n```\n", + [['body' => "parent:\n child: value", 'line' => 2]], + ]; + + yield 'sequence' => [ + "```yaml\nlist:\n - a\n - b\n```\n", + [['body' => "list:\n - a\n - b", 'line' => 2]], + ]; + + yield 'two blocks with correct lines' => [ + "```yaml\nfoo: 1\n```\n\nsome text\n\n```yaml\nbar: 2\n```\n", + [ + ['body' => 'foo: 1', 'line' => 2], + ['body' => 'bar: 2', 'line' => 8], + ], + ]; + } +} diff --git a/tests/ValidationBaseline.php b/tests/ValidationBaseline.php new file mode 100644 index 0000000000..223669e8a1 --- /dev/null +++ b/tests/ValidationBaseline.php @@ -0,0 +1,75 @@ +|null */ + private ?array $entries = null; + + public function __construct( + private readonly string $baselineFile, + private readonly string $repoRoot, + ) { + } + + public function isInBaseline(string $relativePath, ?string $bodyHash, string $errorMessage): bool + { + foreach ($this->getEntries() as $entry) { + $entryPath = $entry['path'] ?? ''; + + // Path: exact match or trailing-suffix match (allows glob-like partial paths) + if ($relativePath !== $entryPath && !str_ends_with($relativePath, ltrim($entryPath, '/'))) { + continue; + } + + // Hash (optional): must match exactly when provided; line is ignored for matching + if (isset($entry['hash']) && $bodyHash !== null && $entry['hash'] !== $bodyHash) { + continue; + } + + // Message (optional): treated as a regex pattern + if (isset($entry['message']) && !preg_match($entry['message'], $errorMessage)) { + continue; + } + + return true; + } + + return false; + } + + /** + * @return list + */ + private function getEntries(): array + { + if ($this->entries !== null) { + return $this->entries; + } + + if (!file_exists($this->baselineFile)) { + return $this->entries = []; + } + + $parsed = \Symfony\Component\Yaml\Yaml::parseFile($this->baselineFile); + + return $this->entries = $parsed['ignoreErrors'] ?? []; + } +} diff --git a/tests/Yaml/CodeSample.php b/tests/Yaml/CodeSample.php new file mode 100644 index 0000000000..3d582da9b7 --- /dev/null +++ b/tests/Yaml/CodeSample.php @@ -0,0 +1,16 @@ +bodyHash = hash('sha256', $body); + } +} diff --git a/tests/Yaml/YamlSamplesProvider.php b/tests/Yaml/YamlSamplesProvider.php new file mode 100644 index 0000000000..5dbc63da45 --- /dev/null +++ b/tests/Yaml/YamlSamplesProvider.php @@ -0,0 +1,96 @@ + + */ + public function getCodeSampleYaml(): iterable + { + yield from $this->iterateCodeSampleYaml(); + yield from $this->iterateMarkdownYamlBlocks(); + } + + /** + * Yields every .yaml file found recursively under code_samples/. + * + * @return iterable + */ + private function iterateCodeSampleYaml(): iterable + { + $iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator(self::CODE_SAMPLES_DIR, RecursiveDirectoryIterator::SKIP_DOTS) + ); + + /** @var \SplFileInfo $file */ + foreach ($iterator as $file) { + if (!$file->isFile() || $file->getExtension() !== 'yaml') { + continue; + } + + $body = file_get_contents($file->getRealPath()); + + if ($body === false) { + continue; + } + + yield new CodeSample($file->getRealPath(), 0, $body); + } + } + + /** + * Yields every fenced YAML block found in .md files under docs/. + * + * @return iterable + */ + private function iterateMarkdownYamlBlocks(): iterable + { + $extractor = new MarkdownYamlExtractor(); + $iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator(self::DOCS_DIR, RecursiveDirectoryIterator::SKIP_DOTS) + ); + + /** @var \SplFileInfo $file */ + foreach ($iterator as $file) { + if (!$file->isFile() || $file->getExtension() !== 'md') { + continue; + } + + $path = $file->getRealPath(); + $content = file_get_contents($path); + + if ($content === false) { + continue; + } + + foreach ($extractor->extract($content) as $block) { + yield new CodeSample($path, $block['line'], $block['body']); + } + } + } +} diff --git a/tests/Yaml/YamlTest.php b/tests/Yaml/YamlTest.php new file mode 100644 index 0000000000..54b781bca8 --- /dev/null +++ b/tests/Yaml/YamlTest.php @@ -0,0 +1,170 @@ +isInBaseline($filePath, $bodyHash, $e->getMessage())) { + self::markTestSkipped(sprintf( + 'Known baseline issue in %s at line %d: %s', + $filePath, + $line, + $e->getMessage(), + )); + } + + self::fail(sprintf( + 'YAML parse error in %s at line %d [hash:%s]: %s', + $filePath, + $line, + $bodyHash, + $e->getMessage(), + )); + } + + $this->addToAssertionCount(1); + } + + /** + * @param int $line Starting line of the config block (0 for standalone YAML files). + */ + #[DataProvider('provideBundleConfigs')] + public function testBundleConfigurationIsValid( + string $extensionName, + mixed $config, + string $filePath, + int $line, + string $bodyHash + ): void { + $configuration = self::configurationProvider()->createConfiguration($extensionName); + $processor = new Processor(); + + $config = self::configurationProvider()->resolveParameters(is_array($config) ? $config : []); + + try { + $processor->processConfiguration($configuration, [$config]); + } catch (\Exception $e) { + if (self::baseline()->isInBaseline($filePath, $bodyHash, $e->getMessage())) { + self::markTestSkipped(sprintf( + 'Known baseline issue for "%s" in %s:%d: %s', + $extensionName, + $filePath, + $line, + $e->getMessage(), + )); + } + + self::fail(sprintf( + 'Invalid configuration for "%s" in %s:%d [hash:%s] — %s', + $extensionName, + $filePath, + $line, + $bodyHash, + $e->getMessage(), + )); + } + + $this->addToAssertionCount(1); + } + + /** + * Yields all standalone YAML files from code_samples/ plus every fenced + * YAML block extracted from docs Markdown files. + * + * @return iterable + */ + public static function provideYamlSources(): iterable + { + foreach (self::samplesProvider()->getCodeSampleYaml() as $item) { + yield self::makeLabel($item->path, $item->line) => [$item->path, $item->line, $item->body, $item->bodyHash]; + } + } + + /** + * Yields one entry per (extension, config) pair found in YAML files and + * in fenced YAML blocks from docs Markdown files. + * + * @return iterable + */ + public static function provideBundleConfigs(): iterable + { + foreach (self::provideYamlSources() as [$filePath, $line, $body, $bodyHash]) { + $path = self::relativePath($filePath); + try { + $parsed = Yaml::parse($body, Yaml::PARSE_CUSTOM_TAGS); + } catch (\Throwable) { + continue; + } + + if (!is_array($parsed)) { + continue; + } + + foreach ($parsed as $extensionName => $config) { + if (!is_string($extensionName) || !self::configurationProvider()->hasExtension($extensionName)) { + continue; + } + + yield sprintf('%s (%s)', $extensionName, self::makeLabel($path, $line)) => [$extensionName, $config, $path, $line, $bodyHash]; + } + } + } + + private static function configurationProvider(): ConfigurationProvider + { + static $provider = null; + + return $provider ??= new ConfigurationProvider(); + } + + private static function samplesProvider(): YamlSamplesProvider + { + static $provider = null; + + return $provider ??= new YamlSamplesProvider(); + } + + private static function baseline(): ValidationBaseline + { + static $baseline = null; + + return $baseline ??= new ValidationBaseline(self::BASELINE_FILE, realpath(self::REPO_ROOT)); + } + + private static function makeLabel(string $absolutePath, int $lineNumber): string + { + return ltrim(str_replace(realpath(self::REPO_ROOT), '', $absolutePath), '/') . ':' . $lineNumber; + } + + private static function relativePath(string $absolutePath): string + { + return ltrim(str_replace(realpath(self::REPO_ROOT), '', $absolutePath), '/'); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000000..9374e6f3a2 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,40 @@ +isDir() && $item->getFilename() === 'src') { + $loader->addPsr4('App\\', $item->getRealPath()); + } +} diff --git a/tests/generate-yaml-baseline.php b/tests/generate-yaml-baseline.php new file mode 100644 index 0000000000..4cee77e254 --- /dev/null +++ b/tests/generate-yaml-baseline.php @@ -0,0 +1,151 @@ +#!/usr/bin/env php +/dev/null', + escapeshellarg($phpunitBin), + escapeshellarg($tmpLog), +); + +passthru($cmd); + +if (!file_exists($tmpLog)) { + if ($previousBaseline !== null) { + file_put_contents($outFile, $previousBaseline); + } + fwrite(STDERR, "ERROR: PHPUnit did not produce a JUnit log file.\n"); + exit(1); +} + +$xml = simplexml_load_file($tmpLog); +unlink($tmpLog); + +if ($xml === false) { + fwrite(STDERR, "ERROR: Could not parse JUnit XML.\n"); + exit(1); +} + +$repoRoot = dirname(__DIR__); +$entries = []; + +// Collect all failures/errors at any nesting depth using XPath +/** @var \SimpleXMLElement[] $testcases */ +$testcases = $xml->xpath('//testcase[failure or error]') ?: []; + +foreach ($testcases as $testcase) { + $failure = $testcase->failure ?? $testcase->error ?? null; + if ($failure === null) { + continue; + } + + $message = (string) $failure; + // Extract file path and line from failure message + // Patterns: + // "Invalid configuration for "X" in path/to/file.yaml: error" + // "Invalid configuration for "X" in path/to/file.md:123 — error" + // "YAML parse error in path/to/file.md at line 123: error" + $path = null; + $line = null; + $bodyHash = null; + $errorMessage = null; + + if (preg_match('/Invalid configuration for "[^"]*" in ([^\n:]+?):(\d+) \[hash:([a-f0-9]+)\] — (.+)/s', $message, $m)) { + $path = trim($m[1]); + $line = (int) $m[2]; + $bodyHash = $m[3]; + $errorMessage = trim(explode("\n", $m[4])[0]); + } elseif (preg_match('/Invalid configuration for "[^"]*" in ([^\n:]+?): (.+)/s', $message, $m)) { + $path = trim($m[1]); + $errorMessage = trim(explode("\n", $m[2])[0]); + } elseif (preg_match('/YAML parse error in ([^\n]+?) at line (\d+) \[hash:([a-f0-9]+)\]: (.+)/s', $message, $m)) { + $path = trim($m[1]); + $line = (int) $m[2]; + $bodyHash = $m[3]; + $errorMessage = trim(explode("\n", $m[4])[0]); + } + + if ($path === null) { + continue; + } + + // Convert absolute path to relative + if (str_starts_with($path, $repoRoot)) { + $path = ltrim(substr($path, strlen($repoRoot)), '/'); + } + + $entry = ['path' => $path]; + if ($line !== null) { + $entry['line'] = $line; + } + if ($bodyHash !== null) { + $entry['hash'] = $bodyHash; + } + if ($errorMessage !== null) { + // Store as a regex: escape special chars, keep it readable + $entry['message'] = '~' . preg_quote($errorMessage, '~') . '~'; + } + + $key = $path . ':' . ($bodyHash ?? $line ?? ''); + $entries[$key] = $entry; +} + +ksort($entries); + +// Render as YAML manually (keep it readable without needing a YAML library) +$lines = []; +$lines[] = '# Auto-generated by `composer phpunit-update-baseline`. Do not edit manually.'; +$lines[] = '# To suppress a failure: regenerate this file after confirming it is expected.'; +$lines[] = '# To fix a suppressed failure: fix the doc error and regenerate.'; +$lines[] = 'ignoreErrors:'; + +foreach ($entries as $entry) { + $lines[] = ' -'; + $lines[] = sprintf(' path: %s', $entry['path']); + if (isset($entry['line'])) { + $lines[] = sprintf(' line: %d', $entry['line']); + } + if (isset($entry['hash'])) { + $lines[] = sprintf(' hash: %s', $entry['hash']); + } + if (isset($entry['message'])) { + // Wrap message in single quotes, escaping internal single quotes + $msg = str_replace("'", "''", $entry['message']); + $lines[] = sprintf(" message: '%s'", $msg); + } +} + +$content = implode("\n", $lines) . "\n"; +$outFile = __DIR__ . '/yaml-validation-baseline.yaml'; +file_put_contents($outFile, $content); + +$count = count($entries); +echo "Baseline written to tests/yaml-validation-baseline.yaml ({$count} entries)\n"; +if ($count === 0) { + echo "No failures found — baseline is empty. All tests pass!\n"; +} diff --git a/tests/yaml-validation-baseline.yaml b/tests/yaml-validation-baseline.yaml new file mode 100644 index 0000000000..ac021c0d51 --- /dev/null +++ b/tests/yaml-validation-baseline.yaml @@ -0,0 +1,394 @@ +# Auto-generated by `composer yaml-update-baseline`. Do not edit manually. +# To suppress a failure: regenerate this file after confirming it is expected. +# To fix a suppressed failure: fix the doc error and regenerate. +ignoreErrors: + - + path: code_samples/forms/custom_form_attribute/config/custom_services.yaml + line: 0 + hash: 9d00873c2b34dc269f7846d4f4462a2a9d0308566818ca845c802d0778d91469 + message: '~Unable to parse at line 1 \(near " App\\FormBuilder\\FieldType\\Field\\Mapper\\CheckboxWithRichtextDescriptionFieldMapper\:"\)\.~' + - + path: code_samples/workflow/custom_workflow/config/packages/workflows.yaml + line: 0 + hash: 48b8c1ee7a1f481c5c83d1d90ed43723cc8dd59fac24adc798f0492d7fac7a89 + message: '~The child config "stages" under "ibexa\.system\.default\.workflows\.quick_review" must be configured\.~' + - + path: docs/administration/back_office/back_office_elements/extending_thumbnails.md + line: 109 + hash: 65a44481f05186cf38bb2efd4583c49faf2b58dc4c197d40a611cd8610a77d97 + message: '~Unable to parse at line 1 \(near " App\\Thumbnails\\FieldValueUrl\:"\)\.~' + - + path: docs/administration/back_office/configure_product_tour.md + line: 26 + hash: 4cfc5ac559eb8db38edb6c8a29872883a95886b61cdae2b343a5ebaf4a9bca8e + message: '~The value "\" is not allowed for path "ibexa\.system\.\\>\.product_tour\.\\.type"\. Permissible values\: "general", "targetable"\.~' + - + path: docs/cdp/cdp_data_customization.md + line: 29 + hash: bb2ecc0b96ca61cd004b9c1806e78d6afc3197a68acb723db7a52cfd5bdc89d6 + message: '~Unable to parse at line 1 \(near " App\\Export\\User\\DateOfBirthUserItemProcessor\:"\)\.~' + - + path: docs/commerce/checkout/customize_checkout.md + line: 130 + hash: fb889cba0ffc081e7d0b0387d32835ae8e5d3c033f26144cb4d54b3e121f428a + message: '~Invalid configuration for path "framework\.workflows\.workflows\.ibexa_checkout"\: "supports" or "support_strategy" should be configured\.~' + - + path: docs/commerce/order_management/configure_order_management.md + line: 51 + hash: 30a04aef21c825d6b59c938c2b5bb4078abd7156526a7f9f1b7b13203ff56ed7 + message: '~The child config "transitions" under "framework\.workflows\.workflows\.ibexa_order" must be configured\.~' + - + path: docs/commerce/payment/enable_paypal_payments.md + line: 40 + hash: bc714d63a541c63d24e2a882cc8c67b685fcee24feb0d80a327f02a4c3b0bef8 + message: '~Unrecognized option "payment_method" under "ibexa"\. Available options are "http_cache", "image_placeholder", "imagemagick", "locale_conversion", "orm", "repositories", "router", "siteaccess", "system", "ui", "url_alias", "url_wildcards"\.~' + - + path: docs/commerce/payment/enable_stripe_payments.md + line: 41 + hash: 09750e5c042fe1d6abc20d4088674195d78ca70ce113f8869818a76de1a8023a + message: '~Unrecognized option "payment_method" under "ibexa"\. Available options are "http_cache", "image_placeholder", "imagemagick", "locale_conversion", "orm", "repositories", "router", "siteaccess", "system", "ui", "url_alias", "url_wildcards"\.~' + - + path: docs/commerce/payment/payum_integration.md + line: 55 + hash: c3f14ff441a95545c360aaab308050a2629a81f07d76d4fb65133ab89a6ad30d + message: '~Unrecognized option "payment_method" under "ibexa"\. Available options are "http_cache", "image_placeholder", "imagemagick", "locale_conversion", "orm", "repositories", "router", "siteaccess", "system", "ui", "url_alias", "url_wildcards"\.~' + - + path: docs/commerce/transactional_emails/extend_transactional_emails.md + line: 17 + hash: f7073192f3b22201fe8d86bb71c76b4026f14efbfe6bb28ddbed657d93eca29c + message: '~The child config "transitions" under "framework\.workflows\.workflows\.ibexa_payment" must be configured\.~' + - + path: docs/content_management/collaborative_editing/configure_collaborative_editing.md + line: 48 + hash: 629d37dcc688c9b11daec8de8c601522830f3b2175ac6ac692db3f17b89a01d7 + message: '~The child config "firewalls" under "security" must be configured\.~' + - + path: docs/content_management/collaborative_editing/configure_collaborative_editing.md + line: 78 + hash: c6488fad354e039350fd0c18bdd0b6dc2a1ff7300dbae9151155490278b840a0 + message: '~Invalid type for path "ibexa\.repositories\.\\.collaboration\.participants\.auto_invite"\. Expected "bool", but got "string"\.~' + - + path: docs/content_management/data_migration/data_migration_actions.md + line: 156 + hash: 023288ce392e2e051ac32ea3525bdecebc301a860789c8d2128042885122bfa9 + message: '~Unable to parse at line 1 \(near " actions\:"\)\.~' + - + path: docs/content_management/data_migration/data_migration_actions.md + line: 102 + hash: 29c25d5ed7cc6b7c435b3d2b6f4e64946ea4f37a27b1e32330a205757a74e56e + message: '~Unable to parse at line 1 \(near " actions\:"\)\.~' + - + path: docs/content_management/data_migration/data_migration_actions.md + line: 136 + hash: 379c053c8d40a701b0931b2e753d1256ece028d3c2e96a86fad3c884d480a48f + message: '~Unable to parse at line 1 \(near " actions\:"\)\.~' + - + path: docs/content_management/data_migration/data_migration_actions.md + line: 78 + hash: 6ebbedc8bf843bdc28aa23e5604ad9795153e5abcf808839229b08a37386f7eb + message: '~Unable to parse at line 1 \(near " actions\:"\)\.~' + - + path: docs/content_management/data_migration/data_migration_actions.md + line: 178 + hash: 93777839119c361b8b3a2708d47240128ef27643dce015decc1bf4edf0e8cb8f + message: '~Unable to parse at line 1 \(near " actions\:"\)\.~' + - + path: docs/content_management/data_migration/data_migration_actions.md + line: 114 + hash: c730d746b624933585bd7f985fc773625750b8385a3755e7735d50e49e4964b2 + message: '~Unable to parse at line 1 \(near " actions\:"\)\.~' + - + path: docs/content_management/data_migration/data_migration_actions.md + line: 86 + hash: d330a24eee42024868c98fcffb2c446706570fea05d05ec3b7309297ba49e3b4 + message: '~Unable to parse at line 1 \(near " actions\:"\)\.~' + - + path: docs/content_management/data_migration/data_migration_actions.md + line: 96 + hash: e4d25af95785fe407cc135a608c778ff3232db0ce36e0bf5ce2039b249e29dc7 + message: '~Unable to parse at line 1 \(near " actions\:"\)\.~' + - + path: docs/content_management/data_migration/importing_data.md + line: 200 + hash: 5fff20465655d9e52a258e3c1b1347a62b0f558d9e733bb377c003d5021822eb + message: '~Unable to parse at line 1 \(near " \- fieldDefIdentifier\: show_children"\)\.~' + - + path: docs/content_management/data_migration/importing_data.md + line: 228 + hash: 7cec0a6d723dcb0d3d87904910e4870f05b885764116e447b8a4fe19025b8289 + message: '~Unable to parse at line 1 \(near " \- fieldDefIdentifier\: project_directory"\)\.~' + - + path: docs/content_management/data_migration/importing_data.md + line: 220 + hash: a08babaebc91ceb742a9a81a04be4d9e975291019794a0658bef1f99e62dccea + message: '~Unable to parse at line 1 \(near " \- fieldDefIdentifier\: some_field"\)\.~' + - + path: docs/content_management/data_migration/importing_data.md + line: 313 + hash: c94bdd7b27818ae0138b8c5bdba1ff33d77e25918609c1741f2c4d4236dcaff1 + message: '~Unable to parse at line 1 \(near " \- fieldDefIdentifier\: image"\)\.~' + - + path: docs/content_management/taxonomy/taxonomy.md + line: 194 + hash: 968846dbeb558b9eb914194845153634b45d8a993977bc321fc9df8e3d29d9cb + message: '~The child config "default_embedding_max_tokens" under "ibexa_taxonomy\.text_to_taxonomy" must be configured\: Maximum number of tokens sent when generating embeddings~' + - + path: docs/content_management/workflow/workflow.md + line: 135 + hash: 0fdf8c632c4f5abed2f8af4fa177bf8afd9b129a48ff1fb611923135b856f865 + message: '~The child config "matcher_value_templates" under "ibexa\.system\.default\.workflows_config" must be configured\: Matcher templates configuration\.~' + - + path: docs/customer_management/cp_page_builder.md + line: 35 + hash: 6915e3e4985cf6b8d99ae25c59ddf2f735fa927fdddb4b2a9e095475b86d02db + message: '~The child config "default_siteaccess" under "ibexa\.siteaccess" must be configured\: Name of the default siteaccess~' + - + path: docs/customer_management/cp_page_builder.md + line: 119 + hash: 833557b1406f91474d3420c809f8cf7ffc7ebefcf12aa37e66d31c291c73a2e6 + message: '~The child config "default_siteaccess" under "ibexa\.siteaccess" must be configured\: Name of the default siteaccess~' + - + path: docs/customer_management/cp_page_builder.md + line: 207 + hash: 98de256c0b149da15bea944ff56da3dad8bf968f5aad2d2882855d8844541dc1 + message: '~The child config "list" under "ibexa\.siteaccess" must be configured\: Available SiteAccess list~' + - + path: docs/discounts/extend_discounts.md + line: 229 + hash: 06089de0d522e5dc8e3a4973eeba57034d001867540768e1805211242768f23a + message: '~Unable to parse at line 1 \(near " App\\Discounts\\RecentDiscountPrioritizationStrategy\:"\)\.~' + - + path: docs/discounts/extend_discounts.md + line: 64 + hash: 24653d7f59ac0fca6cb86161ebf94516d356563f36bc69a65d1e210a13cba6e2 + message: '~Unable to parse at line 1 \(near " App\\Discounts\\ExpressionProvider\\CurrentUserRegistrationDateResolver\:"\)\.~' + - + path: docs/discounts/extend_discounts.md + line: 187 + hash: 415de44b6fd825b157660aec16d415842cb0dadb636b1c0014ba167fc1115973 + message: '~Unable to parse at line 1 \(near " App\\Discounts\\Rule\\PurchasingPowerParityRuleFactory\:"\)\.~' + - + path: docs/discounts/extend_discounts.md + line: 211 + hash: 8459adf86ffd3f1fe5cccf1c12d887d783c6b6e85be7dfc77ea6c3229ba9569e + message: '~Unable to parse at line 1 \(near " App\\Discounts\\Rule\\PurchaseParityValueFormatter\:"\)\.~' + - + path: docs/discounts/extend_discounts.md + line: 82 + hash: 9bf9dc0f20ec840e7c362adf00680d1e67d66f9a1fe6788d32950b3dd250feae + message: '~Unable to parse at line 1 \(near " App\\Discounts\\ExpressionProvider\\IsAnniversaryResolver\:"\)\.~' + - + path: docs/discounts/extend_discounts.md + line: 139 + hash: e049b49c97fe7c51ab27f584e15f9283a83664ed3b4d8b2fcae1866a15d46c72 + message: '~Unable to parse at line 1 \(near " App\\Discounts\\Condition\\IsAccountAnniversaryConditionFactory\:"\)\.~' + - + path: docs/discounts/extend_discounts_wizard.md + line: 142 + hash: 2e79e1bc175f608e6b74a9e28e9145367f4210f44e254c4f188911df6d368645 + message: '~Unable to parse at line 1 \(near " App\\Form\\FormMapper\\PurchasingPowerParityValueMapper\: \~"\)\.~' + - + path: docs/getting_started/first_steps.md + line: 120 + hash: d6956fc656de2808d27e294db9f4a5a1a66630f046ea21b8e75da2f658984f99 + message: '~The child config "default_siteaccess" under "ibexa\.siteaccess" must be configured\: Name of the default siteaccess~' + - + path: docs/infrastructure_and_maintenance/cache/http_cache/reverse_proxy.md + line: 145 + hash: 15a7e49dc68c59afa39e10276a448edf6f9e501374b2a75d43c0edaa7882f5e2 + message: '~The child config "fastly" under "ibexa\.system\.my_siteaccess_group\.http_cache" must be configured\.~' + - + path: docs/infrastructure_and_maintenance/cache/http_cache/reverse_proxy.md + line: 128 + hash: 68ea24e38b23f9efbfe4d689cffb5eb147ea6f332dc334fecc3f25f0117c4fb8 + message: '~The child config "fastly" under "ibexa\.system\.my_siteaccess_group\.http_cache" must be configured\.~' + - + path: docs/infrastructure_and_maintenance/security/development_security.md + line: 20 + hash: 0521cbcc27827c7ee2160a512158a19c89f1d51eb64a69d1b4600eb45a2505dd + message: '~Unrecognized option "require_previous_session" under "security\.firewalls\.ibexa_front\.form_login"\. Available options are "always_use_default_target_path", "check_path", "csrf_parameter", "csrf_token_id", "default_target_path", "enable_csrf", "failure_forward", "failure_handler", "failure_path", "failure_path_parameter", "form_only", "login_path", "password_parameter", "post_only", "provider", "remember_me", "success_handler", "target_path_parameter", "use_forward", "use_referer", "username_parameter"\.~' + - + path: docs/infrastructure_and_maintenance/security/security_checklist.md + line: 142 + hash: 65986b42415e23ed46aec11a3fb7398b138665b9fb75432e1457c7ebf89d18ec + message: '~The child config "firewalls" under "security" must be configured\.~' + - + path: docs/multisite/languages/languages.md + line: 78 + hash: 7d34665cd5af51823e88b55935ea40dfa550684bf2c3af7eba20349adfbb6be4 + message: '~The child config "match" under "ibexa\.siteaccess" must be configured\: Siteaccess match configuration\. First key is the matcher class, value is passed to the matcher\. Key can be a service identifier \(prepended by "@"\), or a FQ class name \(prepended by "\\"\)~' + - + path: docs/multisite/languages/languages.md + line: 135 + hash: 85fc7f295269781688143689f90a32b0f08231b74037ba73bf15d088108492d8 + message: '~The child config "match" under "ibexa\.siteaccess" must be configured\: Siteaccess match configuration\. First key is the matcher class, value is passed to the matcher\. Key can be a service identifier \(prepended by "@"\), or a FQ class name \(prepended by "\\"\)~' + - + path: docs/multisite/multisite_configuration.md + line: 57 + hash: 15dee03480731709d21c89105139a95add1377abb22ea78061b47fff1e013bba + message: '~The child config "list" under "ibexa\.siteaccess" must be configured\: Available SiteAccess list~' + - + path: docs/multisite/multisite_configuration.md + line: 28 + hash: 306a74d1cc587b13c8d3378a9b05815d16cfe0a0e10f9766685e8f39e81f98fd + message: '~The child config "list" under "ibexa\.siteaccess" must be configured\: Available SiteAccess list~' + - + path: docs/multisite/multisite_configuration.md + line: 69 + hash: c5a5cb7578b56189855285f63f16910ef708767ddc9389730d537f97725b5a89 + message: '~The child config "list" under "ibexa\.siteaccess" must be configured\: Available SiteAccess list~' + - + path: docs/multisite/set_up_translation_siteaccess.md + line: 43 + hash: 4038aedf899c1fc78ce0a57750c6c8dc31a317d0178ea3e612554da1e4bbe564 + message: '~The child config "default_siteaccess" under "ibexa\.siteaccess" must be configured\: Name of the default siteaccess~' + - + path: docs/multisite/site_factory/site_factory.md + line: 58 + hash: a1f0951071e5dec4f362284872b59167d59d3ff180e607e089c8a84488fb2c3d + message: '~The child config "list" under "ibexa\.siteaccess" must be configured\: Available SiteAccess list~' + - + path: docs/multisite/site_factory/site_factory.md + line: 40 + hash: e51a407b99ce4b87b710537f7caf45ad7528f720e4f2a1caf3c67ad647cc83f1 + message: '~The child config "list" under "ibexa\.siteaccess" must be configured\: Available SiteAccess list~' + - + path: docs/multisite/site_factory/site_factory_configuration.md + line: 81 + hash: 8721257146510878c104fbcddf27e0bf14afb71aba069a5a38b31f86b8d90db2 + message: '~The child config "siteaccess_group" under "ibexa_site_factory\.templates\.\" must be configured\.~' + - + path: docs/multisite/siteaccess/siteaccess_matching.md + line: 72 + hash: 1713ad609895c28fec2118385b9ec1605a7a299f4909c2fbd5c1363006fdb8ed + message: '~The child config "list" under "ibexa\.siteaccess" must be configured\: Available SiteAccess list~' + - + path: docs/multisite/siteaccess/siteaccess_matching.md + line: 178 + hash: 1b1fddbc3630d435957596321f59276f8fbb9faba14e3bee49749173eddefdc2 + message: '~The child config "list" under "ibexa\.siteaccess" must be configured\: Available SiteAccess list~' + - + path: docs/multisite/siteaccess/siteaccess_matching.md + line: 32 + hash: 2a4a8871fdaf5ba2c788513e81ef70506902f198baa2bc451e28c0e1e20f4c67 + message: '~The child config "list" under "ibexa\.siteaccess" must be configured\: Available SiteAccess list~' + - + path: docs/multisite/siteaccess/siteaccess_matching.md + line: 91 + hash: 691944c34e02c072fbca08e59c43ede5fbd019d1cefc776cd18a512927892d47 + message: '~The child config "list" under "ibexa\.siteaccess" must be configured\: Available SiteAccess list~' + - + path: docs/multisite/siteaccess/siteaccess_matching.md + line: 123 + hash: 78630816bedadf40ecf85287910909a1cfc6b6beebd398807581ae4c3d06ca4a + message: '~The child config "list" under "ibexa\.siteaccess" must be configured\: Available SiteAccess list~' + - + path: docs/multisite/siteaccess/siteaccess_matching.md + line: 108 + hash: 8946bcd67708bd70885f51c5731ee749a649eb66844f1d69cdc18cb81705083a + message: '~The child config "list" under "ibexa\.siteaccess" must be configured\: Available SiteAccess list~' + - + path: docs/multisite/siteaccess/siteaccess_matching.md + line: 194 + hash: a1f0951071e5dec4f362284872b59167d59d3ff180e607e089c8a84488fb2c3d + message: '~The child config "list" under "ibexa\.siteaccess" must be configured\: Available SiteAccess list~' + - + path: docs/multisite/siteaccess/siteaccess_matching.md + line: 140 + hash: ac6a06d9a33e4dcd9c73d2077eca6c6b3398b93cfda729ec7b18f24a45dcc976 + message: '~The child config "list" under "ibexa\.siteaccess" must be configured\: Available SiteAccess list~' + - + path: docs/multisite/siteaccess/siteaccess_matching.md + line: 161 + hash: bca418b7db9ad6c24b726a098611f793080df90e6a77bd71b378b7c2827b8026 + message: '~The child config "list" under "ibexa\.siteaccess" must be configured\: Available SiteAccess list~' + - + path: docs/multisite/siteaccess/siteaccess_matching.md + line: 237 + hash: e6259973305f42d67138e6b99f919b28b817968c03743bc8533540a5d3453d12 + message: '~The child config "list" under "ibexa\.siteaccess" must be configured\: Available SiteAccess list~' + - + path: docs/permissions/limitation_reference.md + line: 28 + hash: c989b346cda18ba902f44d6dfb2aa9129a1da96c87fcd7be9df0f84710155ebc + message: '~Unable to parse at line 3 \(near " ibexa\.api\.role\.limitation_type\.function_list\:"\)\.~' + - + path: docs/personalization/attribute_search_in_elasticsearch.md + line: 16 + hash: 4109336477914bb243c21073b32efd591f427b50f63f9305c39386055b915cd4 + message: '~Invalid output type in \{"\"\:\{"title"\:"\"\}\}\. Output type id should be type of int\.~' + - + path: docs/product_catalog/enable_purchasing_products.md + line: 106 + hash: 86fafb34287eaa9ce0fc52bec555e34c8e91f61778421cff734c8d42eb40e300 + message: '~Unable to parse at line 1 \(near " none\:"\)\.~' + - + path: docs/product_catalog/product_catalog_configuration.md + line: 77 + hash: 86fafb34287eaa9ce0fc52bec555e34c8e91f61778421cff734c8d42eb40e300 + message: '~Unable to parse at line 1 \(near " none\:"\)\.~' + - + path: docs/recommendations/raptor_integration/tracking_functions.md + line: 33 + hash: c2988244a58202f4c2260adddb526aee1315ea134e47dc4cef1fec2aa4bded02 + message: '~Duplicate key "connector_raptor" detected at line 6 \(near " tracking_type\: ''client'' \# Returns \ tags"\)\.~' + - + path: docs/release_notes/ez_platform_v2.4.md + line: 221 + hash: ce8c6170b6011d2a9c41b665109aaaafbcbfacc10d9d5b047c53647a10e58b84 + message: '~Unrecognized option "require_previous_session" under "security\.firewalls\.ezpublish_front\.form_login"\. Available options are "always_use_default_target_path", "check_path", "csrf_parameter", "csrf_token_id", "default_target_path", "enable_csrf", "failure_forward", "failure_handler", "failure_path", "failure_path_parameter", "form_only", "login_path", "password_parameter", "post_only", "provider", "remember_me", "success_handler", "target_path_parameter", "use_forward", "use_referer", "username_parameter"\.~' + - + path: docs/templating/design_engine/add_new_design.md + line: 16 + hash: 00526df40a7b678b4caa97a887e2c6a47689cf3e2e3d52ff42b7ef1b9a74aa0d + message: '~The child config "match" under "ibexa\.siteaccess" must be configured\: Siteaccess match configuration\. First key is the matcher class, value is passed to the matcher\. Key can be a service identifier \(prepended by "@"\), or a FQ class name \(prepended by "\\"\)~' + - + path: docs/templating/templates/template_configuration.md + line: 99 + hash: 2c225195371a55292ddf28e3ab7ef44855d194211bf48753fc65da1a93ef112c + message: '~Duplicate key "match" detected at line 3 \(near "match\: \[\]"\)\.~' + - + path: docs/update_and_migration/from_3.3/to_4.0.md + line: 237 + hash: 95c533291d7910e0abb29943e38be57ca5d60b5535ae4f051bbf8d5e0f5c7497 + message: '~Unrecognized option "ezrichtext" under "ibexa\.system\.admin_group\.fieldtypes"\. Available options are "ibexa_image_asset", "ibexa_richtext"\.~' + - + path: docs/update_and_migration/from_4.3/update_from_4.3_old_commerce.md + line: 168 + hash: 4bde18b55236541ba54dfba017b724848ef5b509a1ffa59f367dd64c45180136 + message: '~The child config "list" under "ibexa\.siteaccess" must be configured\: Available SiteAccess list~' + - + path: docs/update_and_migration/from_4.6/update_from_4.6.md + line: 513 + hash: a773b166515790f4dbce61a47e2553e73de5c4040e3aa3c21d8b63b3e8bfb688 + message: '~Unrecognized option "trace" under "ibexa_elasticsearch\.connections\.default"\. Available options are "authentication", "connection_pool", "connection_selector", "debug", "elastic_cloud_id", "hosts", "index_templates", "node_pool_resurrect", "node_pool_selector", "retries", "ssl"\.~' + - + path: docs/update_and_migration/from_4.6/update_from_4.6.md + line: 80 + hash: be04e17ca47844960603374e75d17e91e15afb8d6a74be42a7fab5b156121ab6 + message: '~Unrecognized option "Ibexa\\Contracts\\Shipping\\Notification\\ShipmentStatusChange" under "ibexa\.system\.my_siteacces_name\.notifications\.subscriptions"\. Available option is "timeout"\.~' + - + path: docs/update_and_migration/from_5.0/update_from_5.0.md + line: 238 + hash: 105a4065685d2da256b76b8b6f35f0893e0b04e278ed03445c3df07de6b6182f + message: '~Unrecognized option "trace" under "ibexa_elasticsearch\.connections\.default"\. Available options are "authentication", "connection_pool", "connection_selector", "debug", "elastic_cloud_id", "hosts", "index_templates", "node_pool_resurrect", "node_pool_selector", "retries", "ssl"\.~' + - + path: docs/update_and_migration/migrate_to_ibexa_dxp/migrating_from_ez_publish_platform.md + line: 540 + hash: 0578015f89f42b10f703e4fa171d2283db34626cb66fe4802663cd46faf95e4f + message: '~Mapping values are not allowed in multi\-line blocks at line 2 \(near " ezpublish\.persistence\.slug_converter\:"\)\.~' + - + path: docs/update_and_migration/migrate_to_ibexa_dxp/migrating_from_ez_publish_platform.md + line: 551 + hash: 1c88285aea32c1ee15509cdb4f296773e3e47c804b26699e98a1c6dac51eb66f + message: '~Unable to parse at line 1 \(near " ezpublish\.persistence\.slug_converter\:"\)\.~' + - + path: docs/users/oauth_server.md + line: 78 + hash: 44b149828628395857cf579b36ec8b838e136e016e372061af2499c567c408ae + message: '~The child config "firewalls" under "security" must be configured\.~' + - + path: docs/users/user_authentication.md + line: 41 + hash: e2b6b72606d335bd6ea3e22e20264ec836ef310e253d9fb371474f35dc8a3c75 + message: '~Unrecognized option "encoders" under "security"\. Available options are "access_control", "access_decision_manager", "access_denied_url", "erase_credentials", "expose_security_errors", "firewalls", "hide_user_not_found", "password_hashers", "providers", "role_hierarchy", "session_fixation_strategy"\.~'